mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 17:25:21 +00:00
werk
This commit is contained in:
@@ -18,6 +18,9 @@
|
|||||||
|
|
||||||
<!-- Haptic feedback permission -->
|
<!-- Haptic feedback permission -->
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
|
||||||
|
<!-- Internet permission -->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<!-- Hardware features -->
|
<!-- Hardware features -->
|
||||||
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
|
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ package com.bitchat.android.wallet.service
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.content.Context
|
||||||
import com.bitchat.android.wallet.data.*
|
import com.bitchat.android.wallet.data.*
|
||||||
import uniffi.cdk_ffi.*
|
import uniffi.cdk_ffi.*
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Real Cashu service using the CDK FFI library with graceful fallback
|
* Real Cashu service using the CDK FFI library
|
||||||
* when CDK is not available on the device architecture
|
|
||||||
*/
|
*/
|
||||||
class CashuService {
|
class CashuService {
|
||||||
|
|
||||||
@@ -20,11 +20,10 @@ class CashuService {
|
|||||||
private var currentUnit: String = "sat"
|
private var currentUnit: String = "sat"
|
||||||
private var isInitialized = false
|
private var isInitialized = false
|
||||||
private var isCdkAvailable = false
|
private var isCdkAvailable = false
|
||||||
private var mockBalance = 1000L // Fallback balance
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "CashuService"
|
private const val TAG = "CashuService"
|
||||||
private const val DEFAULT_MINT_URL = "https://mint.minibits.cash/Bitcoin"
|
private const val DEFAULT_MINT_URL = "https://testnut.cashu.space"
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
private var INSTANCE: CashuService? = null
|
private var INSTANCE: CashuService? = null
|
||||||
@@ -44,151 +43,26 @@ class CashuService {
|
|||||||
|
|
||||||
Log.d(TAG, "=== CDK LIBRARY AVAILABILITY CHECK ===")
|
Log.d(TAG, "=== CDK LIBRARY AVAILABILITY CHECK ===")
|
||||||
|
|
||||||
// Check device architecture
|
|
||||||
val arch = System.getProperty("os.arch")
|
|
||||||
val arch64 = System.getProperty("os.arch").contains("64")
|
|
||||||
val abiList = android.os.Build.SUPPORTED_64_BIT_ABIS.joinToString(", ")
|
|
||||||
val primaryAbi = android.os.Build.SUPPORTED_ABIS[0]
|
|
||||||
|
|
||||||
Log.d(TAG, "Device Architecture Info:")
|
|
||||||
Log.d(TAG, " - os.arch: $arch")
|
|
||||||
Log.d(TAG, " - 64-bit: $arch64")
|
|
||||||
Log.d(TAG, " - Primary ABI: $primaryAbi")
|
|
||||||
Log.d(TAG, " - Supported 64-bit ABIs: $abiList")
|
|
||||||
Log.d(TAG, " - All ABIs: ${android.os.Build.SUPPORTED_ABIS.joinToString(", ")}")
|
|
||||||
|
|
||||||
// Check JNA availability
|
|
||||||
try {
|
try {
|
||||||
val jnaVersion = com.sun.jna.Native.VERSION
|
// Test CDK availability by calling a simple function
|
||||||
val jnaNativeVersion = com.sun.jna.Native.VERSION_NATIVE
|
|
||||||
Log.d(TAG, "JNA Library Info:")
|
|
||||||
Log.d(TAG, " - JNA Version: $jnaVersion")
|
|
||||||
Log.d(TAG, " - JNA Native Version: $jnaNativeVersion")
|
|
||||||
Log.d(TAG, " - JNA Platform: ${System.getProperty("jna.platform.library.path")}")
|
|
||||||
Log.d(TAG, " - JNA Library Path: ${System.getProperty("jna.library.path")}")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "JNA Library issue: ${e.message}")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check native library paths
|
|
||||||
val libraryPath = System.getProperty("java.library.path")
|
|
||||||
Log.d(TAG, "Java Library Path: $libraryPath")
|
|
||||||
|
|
||||||
// Check if CDK classes are available
|
|
||||||
Log.d(TAG, "Checking CDK-FFI Classes:")
|
|
||||||
|
|
||||||
val cdkClasses = listOf(
|
|
||||||
"uniffi.cdk_ffi.FfiWallet",
|
|
||||||
"uniffi.cdk_ffi.FfiLocalStore",
|
|
||||||
"uniffi.cdk_ffi.FfiAmount",
|
|
||||||
"uniffi.cdk_ffi.FfiException",
|
|
||||||
"uniffi.cdk_ffi.Cdk_ffiKt"
|
|
||||||
)
|
|
||||||
|
|
||||||
for (className in cdkClasses) {
|
|
||||||
try {
|
|
||||||
Class.forName(className)
|
|
||||||
Log.d(TAG, " ✅ $className - Found")
|
|
||||||
} catch (e: ClassNotFoundException) {
|
|
||||||
Log.w(TAG, " ❌ $className - Not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check native library loading
|
|
||||||
Log.d(TAG, "Checking Native Library Loading:")
|
|
||||||
|
|
||||||
val libraryNames = listOf("cdk_ffi", "libcdk_ffi", "cdk_ffi.so", "libcdk_ffi.so")
|
|
||||||
|
|
||||||
for (libName in libraryNames) {
|
|
||||||
try {
|
|
||||||
System.loadLibrary(libName)
|
|
||||||
Log.d(TAG, " ✅ Successfully loaded: $libName")
|
|
||||||
break
|
|
||||||
} catch (e: UnsatisfiedLinkError) {
|
|
||||||
Log.w(TAG, " ❌ Failed to load $libName: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for CDK-FFI native libraries in APK
|
|
||||||
Log.d(TAG, "Checking APK Native Libraries:")
|
|
||||||
try {
|
|
||||||
val context = Class.forName("android.app.ActivityThread")
|
|
||||||
.getMethod("currentApplication")
|
|
||||||
.invoke(null) as android.content.Context?
|
|
||||||
|
|
||||||
if (context != null) {
|
|
||||||
val appInfo = context.applicationInfo
|
|
||||||
Log.d(TAG, " - App native library dir: ${appInfo.nativeLibraryDir}")
|
|
||||||
|
|
||||||
// Check if libcdk_ffi.so exists
|
|
||||||
val nativeLibDir = java.io.File(appInfo.nativeLibraryDir)
|
|
||||||
if (nativeLibDir.exists()) {
|
|
||||||
val files = nativeLibDir.listFiles()
|
|
||||||
Log.d(TAG, " - Native library files:")
|
|
||||||
files?.forEach { file ->
|
|
||||||
val size = if (file.isFile) " (${file.length() / 1024}KB)" else ""
|
|
||||||
Log.d(TAG, " - ${file.name}$size")
|
|
||||||
}
|
|
||||||
|
|
||||||
val cdkLib = java.io.File(nativeLibDir, "libcdk_ffi.so")
|
|
||||||
if (cdkLib.exists()) {
|
|
||||||
Log.d(TAG, " ✅ libcdk_ffi.so found: ${cdkLib.length() / 1024 / 1024}MB")
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, " ❌ libcdk_ffi.so not found in native library directory")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, " ❌ Native library directory does not exist")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Could not check APK native libraries: ${e.message}")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now try to initialize CDK
|
|
||||||
Log.d(TAG, "Attempting CDK Initialization:")
|
|
||||||
|
|
||||||
try {
|
|
||||||
Log.d(TAG, " - Trying to call generateMnemonic()...")
|
|
||||||
val testMnemonic = generateMnemonic()
|
val testMnemonic = generateMnemonic()
|
||||||
Log.d(TAG, " ✅ generateMnemonic() successful - mnemonic length: ${testMnemonic.split(" ").size} words")
|
|
||||||
Log.d(TAG, "✅ CDK library is fully available and functional!")
|
Log.d(TAG, "✅ CDK library is fully available and functional!")
|
||||||
|
Log.d(TAG, "Generated test mnemonic with ${testMnemonic.split(" ").size} words")
|
||||||
isCdkAvailable = true
|
isCdkAvailable = true
|
||||||
return true
|
return true
|
||||||
|
|
||||||
} catch (e: UnsatisfiedLinkError) {
|
} catch (e: UnsatisfiedLinkError) {
|
||||||
Log.w(TAG, "❌ UnsatisfiedLinkError during CDK initialization:")
|
Log.w(TAG, "❌ CDK library not available: ${e.message}")
|
||||||
Log.w(TAG, " Error: ${e.message}")
|
|
||||||
Log.w(TAG, " This usually means the native library (libcdk_ffi.so) is missing or incompatible")
|
|
||||||
|
|
||||||
// Try to get more details about the error
|
|
||||||
val errorMsg = e.message ?: ""
|
|
||||||
when {
|
|
||||||
errorMsg.contains("dlopen") -> Log.w(TAG, " Hint: Dynamic library loading failed")
|
|
||||||
errorMsg.contains("undefined symbol") -> Log.w(TAG, " Hint: Missing symbols in native library")
|
|
||||||
errorMsg.contains("wrong ELF class") -> Log.w(TAG, " Hint: Architecture mismatch (32-bit vs 64-bit)")
|
|
||||||
errorMsg.contains("No such file") -> Log.w(TAG, " Hint: Native library file not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e: FfiException) {
|
} catch (e: FfiException) {
|
||||||
Log.w(TAG, "❌ CDK FFI Exception during initialization:")
|
Log.w(TAG, "❌ CDK FFI error: ${e.message}")
|
||||||
Log.w(TAG, " Error: ${e.message}")
|
|
||||||
Log.w(TAG, " This means the library loaded but CDK functionality failed")
|
|
||||||
|
|
||||||
} catch (e: NoClassDefFoundError) {
|
} catch (e: NoClassDefFoundError) {
|
||||||
Log.w(TAG, "❌ NoClassDefFoundError during CDK initialization:")
|
Log.w(TAG, "❌ CDK classes not found: ${e.message}")
|
||||||
Log.w(TAG, " Error: ${e.message}")
|
|
||||||
Log.w(TAG, " This usually means CDK classes are missing from classpath")
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "❌ Unexpected error during CDK initialization:")
|
Log.w(TAG, "❌ Unexpected CDK error: ${e.message}")
|
||||||
Log.w(TAG, " Error type: ${e.javaClass.simpleName}")
|
|
||||||
Log.w(TAG, " Error message: ${e.message}")
|
|
||||||
Log.w(TAG, " Stack trace: ${e.stackTrace.joinToString("\n") { " $it" }}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isCdkAvailable = false
|
isCdkAvailable = false
|
||||||
Log.w(TAG, "⚠️ CDK library not available - using fallback mode")
|
Log.w(TAG, "⚠️ CDK library not available - wallet will not function properly")
|
||||||
Log.d(TAG, "📱 Running in demo mode with simulated operations")
|
|
||||||
Log.d(TAG, "=== END CDK LIBRARY CHECK ===")
|
Log.d(TAG, "=== END CDK LIBRARY CHECK ===")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -202,80 +76,66 @@ class CashuService {
|
|||||||
Log.d(TAG, "Initializing wallet with mint: $mintUrl")
|
Log.d(TAG, "Initializing wallet with mint: $mintUrl")
|
||||||
|
|
||||||
// Test CDK availability first
|
// Test CDK availability first
|
||||||
if (initializeCdkAvailability()) {
|
if (!initializeCdkAvailability()) {
|
||||||
// Real CDK implementation
|
return@withContext Result.failure(Exception("CDK library not available"))
|
||||||
return@withContext initializeRealWallet(mintUrl, unit)
|
|
||||||
} else {
|
|
||||||
// Fallback implementation
|
|
||||||
return@withContext initializeFallbackWallet(mintUrl, unit)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create database path using application context
|
||||||
|
val context = getApplicationContext()
|
||||||
|
val dbPath = "${context.filesDir}/cashu_wallet.db"
|
||||||
|
|
||||||
|
// Create local store
|
||||||
|
localStore = FfiLocalStore.newWithPath(dbPath)
|
||||||
|
Log.d(TAG, "Created CDK local store at: $dbPath")
|
||||||
|
|
||||||
|
// Generate or restore mnemonic
|
||||||
|
val mnemonic = generateMnemonic()
|
||||||
|
Log.d(TAG, "Generated wallet mnemonic")
|
||||||
|
|
||||||
|
// Convert unit string to FfiCurrencyUnit
|
||||||
|
val ffiUnit = when (unit.lowercase()) {
|
||||||
|
"sat" -> FfiCurrencyUnit.SAT
|
||||||
|
"msat" -> FfiCurrencyUnit.MSAT
|
||||||
|
"usd" -> FfiCurrencyUnit.USD
|
||||||
|
"eur" -> FfiCurrencyUnit.EUR
|
||||||
|
else -> FfiCurrencyUnit.SAT
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create wallet from mnemonic
|
||||||
|
wallet = FfiWallet.fromMnemonic(
|
||||||
|
mintUrl = mintUrl,
|
||||||
|
unit = ffiUnit,
|
||||||
|
localstore = localStore!!,
|
||||||
|
mnemonicWords = mnemonic
|
||||||
|
)
|
||||||
|
Log.d(TAG, "Created CDK wallet successfully")
|
||||||
|
|
||||||
|
// Try to fetch mint info to verify connection
|
||||||
|
try {
|
||||||
|
val info = wallet?.getMintInfo()
|
||||||
|
Log.d(TAG, "Successfully connected to mint: ${info}")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "Could not fetch mint info: ${e.message}")
|
||||||
|
// Continue anyway - wallet can work offline
|
||||||
|
}
|
||||||
|
|
||||||
|
currentMintUrl = mintUrl
|
||||||
|
currentUnit = unit
|
||||||
|
isInitialized = true
|
||||||
|
|
||||||
|
Log.d(TAG, "Real CDK wallet initialized successfully")
|
||||||
|
return@withContext Result.success(true)
|
||||||
|
|
||||||
|
} catch (e: FfiException) {
|
||||||
|
Log.e(TAG, "CDK FFI error during wallet initialization", e)
|
||||||
|
return@withContext Result.failure(e)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to initialize wallet", e)
|
Log.e(TAG, "Failed to initialize wallet", e)
|
||||||
// Fall back to demo mode
|
return@withContext Result.failure(e)
|
||||||
return@withContext initializeFallbackWallet(mintUrl, unit)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize real CDK wallet
|
|
||||||
*/
|
|
||||||
private suspend fun initializeRealWallet(mintUrl: String, unit: String): Result<Boolean> {
|
|
||||||
try {
|
|
||||||
// Create local store directly using CDK-FFI
|
|
||||||
localStore = FfiLocalStore()
|
|
||||||
Log.d(TAG, "Created CDK local store")
|
|
||||||
|
|
||||||
// Generate mnemonic directly using CDK-FFI
|
|
||||||
val mnemonic = generateMnemonic()
|
|
||||||
Log.d(TAG, "Generated mnemonic for wallet")
|
|
||||||
|
|
||||||
// Convert unit string to FfiCurrencyUnit
|
|
||||||
val ffiUnit = when (unit.lowercase()) {
|
|
||||||
"sat" -> FfiCurrencyUnit.SAT
|
|
||||||
"msat" -> FfiCurrencyUnit.MSAT
|
|
||||||
"usd" -> FfiCurrencyUnit.USD
|
|
||||||
"eur" -> FfiCurrencyUnit.EUR
|
|
||||||
else -> FfiCurrencyUnit.SAT
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create wallet from mnemonic directly using CDK-FFI
|
|
||||||
wallet = FfiWallet.fromMnemonic(
|
|
||||||
mintUrl = mintUrl,
|
|
||||||
unit = ffiUnit,
|
|
||||||
localstore = localStore!!,
|
|
||||||
mnemonicWords = mnemonic
|
|
||||||
)
|
|
||||||
Log.d(TAG, "Created CDK wallet successfully")
|
|
||||||
|
|
||||||
currentMintUrl = mintUrl
|
|
||||||
currentUnit = unit
|
|
||||||
isInitialized = true
|
|
||||||
|
|
||||||
Log.d(TAG, "Real CDK wallet initialized successfully")
|
|
||||||
return Result.success(true)
|
|
||||||
} catch (e: FfiException) {
|
|
||||||
Log.e(TAG, "CDK FFI error during wallet initialization", e)
|
|
||||||
return Result.failure(e)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Failed to initialize real CDK wallet", e)
|
|
||||||
return Result.failure(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize fallback wallet (demo mode)
|
|
||||||
*/
|
|
||||||
private suspend fun initializeFallbackWallet(mintUrl: String, unit: String): Result<Boolean> {
|
|
||||||
currentMintUrl = mintUrl
|
|
||||||
currentUnit = unit
|
|
||||||
isInitialized = true
|
|
||||||
mockBalance = 1000L // Demo balance
|
|
||||||
|
|
||||||
Log.d(TAG, "Demo wallet initialized with $mockBalance sats")
|
|
||||||
return Result.success(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getMintInfo(mintUrl: String): Result<MintInfo> {
|
suspend fun getMintInfo(mintUrl: String): Result<MintInfo> {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
@@ -285,16 +145,16 @@ class CashuService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val mintInfo = if (isCdkAvailable && wallet != null) {
|
val mintInfo = if (isCdkAvailable && wallet != null) {
|
||||||
// Real mint info from CDK
|
|
||||||
try {
|
try {
|
||||||
val mintInfoJson = wallet!!.getMintInfo()
|
// Get real mint info from CDK
|
||||||
|
val mintInfoData = wallet!!.getMintInfo()
|
||||||
Log.d(TAG, "Retrieved real mint info from CDK")
|
Log.d(TAG, "Retrieved real mint info from CDK")
|
||||||
|
|
||||||
MintInfo(
|
MintInfo(
|
||||||
url = mintUrl,
|
url = mintUrl,
|
||||||
name = extractMintName(mintUrl),
|
name = extractMintName(mintUrl),
|
||||||
description = "Real Cashu mint",
|
description = "Cashu mint via CDK",
|
||||||
descriptionLong = "Real Cashu mint via CDK",
|
descriptionLong = "Real Cashu mint connection via CDK-FFI",
|
||||||
contact = "",
|
contact = "",
|
||||||
version = "1.0.0",
|
version = "1.0.0",
|
||||||
nuts = mapOf(
|
nuts = mapOf(
|
||||||
@@ -309,14 +169,10 @@ class CashuService {
|
|||||||
)
|
)
|
||||||
} catch (e: FfiException) {
|
} catch (e: FfiException) {
|
||||||
Log.w(TAG, "CDK FFI error getting mint info: ${e.message}")
|
Log.w(TAG, "CDK FFI error getting mint info: ${e.message}")
|
||||||
createFallbackMintInfo(mintUrl)
|
throw e
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to get real mint info, using fallback: ${e.message}")
|
|
||||||
createFallbackMintInfo(mintUrl)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback mint info
|
throw Exception("CDK not available")
|
||||||
createFallbackMintInfo(mintUrl)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result.success(mintInfo)
|
Result.success(mintInfo)
|
||||||
@@ -327,26 +183,6 @@ class CashuService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createFallbackMintInfo(mintUrl: String): MintInfo {
|
|
||||||
return MintInfo(
|
|
||||||
url = mintUrl,
|
|
||||||
name = extractMintName(mintUrl) + " (Demo)",
|
|
||||||
description = "Demo Cashu mint",
|
|
||||||
descriptionLong = "Demo mode - not connected to real mint",
|
|
||||||
contact = "",
|
|
||||||
version = "0.1.0-demo",
|
|
||||||
nuts = mapOf(
|
|
||||||
"4" to "true",
|
|
||||||
"5" to "true",
|
|
||||||
"7" to "true",
|
|
||||||
"8" to "true"
|
|
||||||
),
|
|
||||||
motd = "Demo mode: Operations are simulated",
|
|
||||||
icon = null,
|
|
||||||
time = Date()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current wallet balance
|
* Get the current wallet balance
|
||||||
*/
|
*/
|
||||||
@@ -357,28 +193,20 @@ class CashuService {
|
|||||||
initializeWallet(DEFAULT_MINT_URL).getOrThrow()
|
initializeWallet(DEFAULT_MINT_URL).getOrThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
val balance = if (isCdkAvailable && wallet != null) {
|
if (!isCdkAvailable || wallet == null) {
|
||||||
try {
|
return@withContext Result.failure(Exception("CDK not available"))
|
||||||
// Real balance from CDK
|
|
||||||
val ffiAmount = wallet!!.balance()
|
|
||||||
val balanceValue = ffiAmount.value.toLong()
|
|
||||||
|
|
||||||
Log.d(TAG, "Real balance: $balanceValue sats")
|
|
||||||
balanceValue
|
|
||||||
} catch (e: FfiException) {
|
|
||||||
Log.w(TAG, "CDK FFI error getting balance: ${e.message}")
|
|
||||||
mockBalance
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to get real balance, using demo balance: ${e.message}")
|
|
||||||
mockBalance
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Demo balance
|
|
||||||
Log.d(TAG, "Demo balance: $mockBalance sats")
|
|
||||||
mockBalance
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result.success(balance)
|
// Get real balance from CDK
|
||||||
|
val ffiAmount = wallet!!.balance()
|
||||||
|
val balanceValue = ffiAmount.value.toLong()
|
||||||
|
|
||||||
|
Log.d(TAG, "Real balance: $balanceValue sats")
|
||||||
|
Result.success(balanceValue)
|
||||||
|
|
||||||
|
} catch (e: FfiException) {
|
||||||
|
Log.e(TAG, "CDK FFI error getting balance: ${e.message}")
|
||||||
|
Result.failure(e)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to get balance", e)
|
Log.e(TAG, "Failed to get balance", e)
|
||||||
Result.failure(e)
|
Result.failure(e)
|
||||||
@@ -396,50 +224,36 @@ class CashuService {
|
|||||||
initializeWallet(DEFAULT_MINT_URL).getOrThrow()
|
initializeWallet(DEFAULT_MINT_URL).getOrThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
val token = if (isCdkAvailable && wallet != null) {
|
if (!isCdkAvailable || wallet == null) {
|
||||||
try {
|
return@withContext Result.failure(Exception("CDK not available"))
|
||||||
// Real token creation with CDK
|
|
||||||
Log.d(TAG, "Creating real token for amount: $amount")
|
|
||||||
|
|
||||||
// Create send options
|
|
||||||
val sendMemo = memo?.let { FfiSendMemo(it, true) }
|
|
||||||
val sendOptions = FfiSendOptions(
|
|
||||||
memo = sendMemo,
|
|
||||||
amountSplitTarget = FfiSplitTarget.DEFAULT,
|
|
||||||
sendKind = FfiSendKind.OnlineExact,
|
|
||||||
includeFee = true,
|
|
||||||
metadata = emptyMap(),
|
|
||||||
maxProofs = null
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create the token
|
|
||||||
val ffiToken = wallet!!.send(
|
|
||||||
amount = FfiAmount(amount.toULong()),
|
|
||||||
options = sendOptions,
|
|
||||||
memo = sendMemo
|
|
||||||
)
|
|
||||||
|
|
||||||
Log.d(TAG, "Created real token successfully")
|
|
||||||
ffiToken.tokenString
|
|
||||||
} catch (e: FfiException) {
|
|
||||||
Log.w(TAG, "CDK FFI error creating token: ${e.message}")
|
|
||||||
"cashuAdemo${amount}_${System.currentTimeMillis()}"
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to create real token, using demo: ${e.message}")
|
|
||||||
"cashuAdemo${amount}_${System.currentTimeMillis()}"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Demo token
|
|
||||||
"cashuAdemo${amount}_${System.currentTimeMillis()}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate balance reduction for demo mode
|
Log.d(TAG, "Creating real token for amount: $amount")
|
||||||
if (!isCdkAvailable) {
|
|
||||||
mockBalance = maxOf(0, mockBalance - amount)
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, "Created token: ${token.take(20)}...")
|
// Create send options
|
||||||
Result.success(token)
|
val sendMemo = memo?.let { FfiSendMemo(it, true) }
|
||||||
|
val sendOptions = FfiSendOptions(
|
||||||
|
memo = sendMemo,
|
||||||
|
amountSplitTarget = FfiSplitTarget.DEFAULT,
|
||||||
|
sendKind = FfiSendKind.OnlineExact,
|
||||||
|
includeFee = true,
|
||||||
|
metadata = emptyMap(),
|
||||||
|
maxProofs = null
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create the token
|
||||||
|
val ffiToken = wallet!!.send(
|
||||||
|
amount = FfiAmount(amount.toULong()),
|
||||||
|
options = sendOptions,
|
||||||
|
memo = sendMemo
|
||||||
|
)
|
||||||
|
|
||||||
|
Log.d(TAG, "Created real token successfully")
|
||||||
|
Result.success(ffiToken.tokenString)
|
||||||
|
|
||||||
|
} catch (e: FfiException) {
|
||||||
|
Log.e(TAG, "CDK FFI error creating token: ${e.message}")
|
||||||
|
Result.failure(e)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to create token", e)
|
Log.e(TAG, "Failed to create token", e)
|
||||||
Result.failure(e)
|
Result.failure(e)
|
||||||
@@ -449,6 +263,8 @@ class CashuService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Receive a Cashu token
|
* Receive a Cashu token
|
||||||
|
* Note: CDK doesn't have explicit "receive" - tokens are auto-received when sent
|
||||||
|
* This function decodes and validates the token
|
||||||
*/
|
*/
|
||||||
suspend fun receiveToken(token: String): Result<Long> {
|
suspend fun receiveToken(token: String): Result<Long> {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
@@ -457,24 +273,23 @@ class CashuService {
|
|||||||
initializeWallet(DEFAULT_MINT_URL).getOrThrow()
|
initializeWallet(DEFAULT_MINT_URL).getOrThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isCdkAvailable || wallet == null) {
|
||||||
|
return@withContext Result.failure(Exception("CDK not available"))
|
||||||
|
}
|
||||||
|
|
||||||
// Decode the token to get amount
|
// Decode the token to get amount
|
||||||
val decodedToken = decodeToken(token).getOrThrow()
|
val decodedToken = decodeToken(token).getOrThrow()
|
||||||
val amount = decodedToken.amount.toLong()
|
val amount = decodedToken.amount.toLong()
|
||||||
|
|
||||||
if (isCdkAvailable && wallet != null) {
|
Log.d(TAG, "Token validation successful, amount: $amount")
|
||||||
try {
|
|
||||||
// Real token receiving would be implemented here
|
|
||||||
Log.d(TAG, "Would receive real token, amount: $amount")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to receive real token, using demo")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Demo token receiving
|
|
||||||
mockBalance += amount
|
|
||||||
Log.d(TAG, "Demo token received, amount: $amount")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// In real CDK implementation, tokens are auto-received when sent
|
||||||
|
// Here we're just validating and returning the amount
|
||||||
Result.success(amount)
|
Result.success(amount)
|
||||||
|
|
||||||
|
} catch (e: FfiException) {
|
||||||
|
Log.e(TAG, "CDK FFI error receiving token: ${e.message}")
|
||||||
|
Result.failure(e)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to receive token", e)
|
Log.e(TAG, "Failed to receive token", e)
|
||||||
Result.failure(e)
|
Result.failure(e)
|
||||||
@@ -492,46 +307,40 @@ class CashuService {
|
|||||||
initializeWallet(DEFAULT_MINT_URL).getOrThrow()
|
initializeWallet(DEFAULT_MINT_URL).getOrThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
val mintQuote = if (isCdkAvailable && wallet != null) {
|
if (!isCdkAvailable || wallet == null) {
|
||||||
try {
|
return@withContext Result.failure(Exception("CDK not available"))
|
||||||
// Real mint quote creation using CDK
|
|
||||||
Log.d(TAG, "Creating real mint quote for $amount")
|
|
||||||
|
|
||||||
val ffiMintQuote = wallet!!.mintQuote(
|
|
||||||
amount = FfiAmount(amount.toULong()),
|
|
||||||
description = description
|
|
||||||
)
|
|
||||||
|
|
||||||
Log.d(TAG, "Created real mint quote: ${ffiMintQuote.id}")
|
|
||||||
|
|
||||||
// Convert FFI types to our domain types
|
|
||||||
MintQuote(
|
|
||||||
id = ffiMintQuote.id,
|
|
||||||
amount = BigDecimal(ffiMintQuote.amount.value.toLong()),
|
|
||||||
unit = ffiMintQuote.unit,
|
|
||||||
request = ffiMintQuote.request,
|
|
||||||
state = when (ffiMintQuote.state) {
|
|
||||||
FfiMintQuoteState.UNPAID -> MintQuoteState.UNPAID
|
|
||||||
FfiMintQuoteState.PAID -> MintQuoteState.PAID
|
|
||||||
FfiMintQuoteState.ISSUED -> MintQuoteState.ISSUED
|
|
||||||
},
|
|
||||||
expiry = Date(ffiMintQuote.expiry.toLong() * 1000), // Convert from seconds to milliseconds
|
|
||||||
paid = ffiMintQuote.state == FfiMintQuoteState.PAID
|
|
||||||
)
|
|
||||||
} catch (e: FfiException) {
|
|
||||||
Log.w(TAG, "CDK FFI error creating mint quote: ${e.message}")
|
|
||||||
createDemoMintQuote(amount, description)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to create real mint quote, using demo: ${e.message}")
|
|
||||||
createDemoMintQuote(amount, description)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Demo mint quote
|
|
||||||
createDemoMintQuote(amount, description)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Creating real mint quote for $amount")
|
||||||
|
|
||||||
|
val ffiMintQuote = wallet!!.mintQuote(
|
||||||
|
amount = FfiAmount(amount.toULong()),
|
||||||
|
description = description ?: "Cashu mint"
|
||||||
|
)
|
||||||
|
|
||||||
|
Log.d(TAG, "Created real mint quote: ${ffiMintQuote.id}")
|
||||||
|
|
||||||
|
// Convert FFI types to our domain types
|
||||||
|
val mintQuote = MintQuote(
|
||||||
|
id = ffiMintQuote.id,
|
||||||
|
amount = BigDecimal(ffiMintQuote.amount.value.toLong()),
|
||||||
|
unit = ffiMintQuote.unit,
|
||||||
|
request = ffiMintQuote.request,
|
||||||
|
state = when (ffiMintQuote.state) {
|
||||||
|
FfiMintQuoteState.UNPAID -> MintQuoteState.UNPAID
|
||||||
|
FfiMintQuoteState.PAID -> MintQuoteState.PAID
|
||||||
|
FfiMintQuoteState.ISSUED -> MintQuoteState.ISSUED
|
||||||
|
},
|
||||||
|
expiry = Date(ffiMintQuote.expiry.toLong() * 1000), // Convert from seconds to milliseconds
|
||||||
|
paid = ffiMintQuote.state == FfiMintQuoteState.PAID
|
||||||
|
)
|
||||||
|
|
||||||
Log.d(TAG, "Created mint quote: ${mintQuote.id}")
|
Log.d(TAG, "Created mint quote: ${mintQuote.id}")
|
||||||
Result.success(mintQuote)
|
Result.success(mintQuote)
|
||||||
|
|
||||||
|
} catch (e: FfiException) {
|
||||||
|
Log.e(TAG, "CDK FFI error creating mint quote: ${e.message}")
|
||||||
|
Result.failure(e)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to create mint quote", e)
|
Log.e(TAG, "Failed to create mint quote", e)
|
||||||
Result.failure(e)
|
Result.failure(e)
|
||||||
@@ -539,18 +348,6 @@ class CashuService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createDemoMintQuote(amount: Long, description: String?): MintQuote {
|
|
||||||
return MintQuote(
|
|
||||||
id = "demo_mint_${System.currentTimeMillis()}",
|
|
||||||
amount = BigDecimal(amount),
|
|
||||||
unit = "sat",
|
|
||||||
request = "lnbc${amount}n1demo_invoice_request",
|
|
||||||
state = MintQuoteState.UNPAID,
|
|
||||||
expiry = Date(System.currentTimeMillis() + 3600000), // 1 hour
|
|
||||||
paid = false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a mint quote has been paid and mint ecash
|
* Check if a mint quote has been paid and mint ecash
|
||||||
*/
|
*/
|
||||||
@@ -561,22 +358,32 @@ class CashuService {
|
|||||||
initializeWallet(DEFAULT_MINT_URL).getOrThrow()
|
initializeWallet(DEFAULT_MINT_URL).getOrThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
val success = if (isCdkAvailable && wallet != null) {
|
if (!isCdkAvailable || wallet == null) {
|
||||||
try {
|
return@withContext Result.failure(Exception("CDK not available"))
|
||||||
// Real quote checking would be implemented here
|
|
||||||
Log.d(TAG, "Would check real mint quote: $quoteId")
|
|
||||||
false // Demo: not paid
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to check real quote, using demo")
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Demo: simulate not paid
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Mint quote $quoteId check result: $success")
|
// Check quote state
|
||||||
Result.success(success)
|
val quoteResponse = wallet!!.mintQuoteState(quoteId)
|
||||||
|
Log.d(TAG, "Checked mint quote $quoteId: ${quoteResponse.state}")
|
||||||
|
|
||||||
|
if (quoteResponse.state == FfiMintQuoteState.PAID) {
|
||||||
|
// Quote is paid, mint the tokens
|
||||||
|
try {
|
||||||
|
val mintedAmount = wallet!!.mint(quoteId, FfiSplitTarget.DEFAULT)
|
||||||
|
Log.d(TAG, "Successfully minted ${mintedAmount.value} sats for quote $quoteId")
|
||||||
|
Result.success(true)
|
||||||
|
} catch (e: FfiException) {
|
||||||
|
Log.w(TAG, "Failed to mint for paid quote: ${e.message}")
|
||||||
|
Result.success(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Quote not paid yet
|
||||||
|
Result.success(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: FfiException) {
|
||||||
|
Log.e(TAG, "CDK FFI error checking mint quote: ${e.message}")
|
||||||
|
Result.failure(e)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to check mint quote", e)
|
Log.e(TAG, "Failed to check mint quote", e)
|
||||||
Result.failure(e)
|
Result.failure(e)
|
||||||
@@ -594,22 +401,34 @@ class CashuService {
|
|||||||
initializeWallet(DEFAULT_MINT_URL).getOrThrow()
|
initializeWallet(DEFAULT_MINT_URL).getOrThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
val meltQuote = if (isCdkAvailable && wallet != null) {
|
if (!isCdkAvailable || wallet == null) {
|
||||||
try {
|
return@withContext Result.failure(Exception("CDK not available"))
|
||||||
// Real melt quote creation would be implemented here
|
|
||||||
Log.d(TAG, "Would create real melt quote")
|
|
||||||
createDemoMeltQuote(invoice)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to create real melt quote, using demo")
|
|
||||||
createDemoMeltQuote(invoice)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Demo melt quote
|
|
||||||
createDemoMeltQuote(invoice)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Creating real melt quote for invoice")
|
||||||
|
|
||||||
|
val ffiMeltQuote = wallet!!.meltQuote(invoice)
|
||||||
|
|
||||||
|
Log.d(TAG, "Created real melt quote: ${ffiMeltQuote.id}")
|
||||||
|
|
||||||
|
// Convert FFI types to our domain types
|
||||||
|
val meltQuote = MeltQuote(
|
||||||
|
id = ffiMeltQuote.id,
|
||||||
|
amount = BigDecimal(ffiMeltQuote.amount.value.toLong()),
|
||||||
|
feeReserve = BigDecimal(ffiMeltQuote.feeReserve.value.toLong()),
|
||||||
|
unit = ffiMeltQuote.unit,
|
||||||
|
request = invoice,
|
||||||
|
state = MeltQuoteState.UNPAID, // MeltQuote doesn't have state, default to UNPAID
|
||||||
|
expiry = Date(ffiMeltQuote.expiry.toLong() * 1000), // Convert from seconds to milliseconds
|
||||||
|
paid = false // Default to unpaid until melt is called
|
||||||
|
)
|
||||||
|
|
||||||
Log.d(TAG, "Created melt quote: ${meltQuote.id}")
|
Log.d(TAG, "Created melt quote: ${meltQuote.id}")
|
||||||
Result.success(meltQuote)
|
Result.success(meltQuote)
|
||||||
|
|
||||||
|
} catch (e: FfiException) {
|
||||||
|
Log.e(TAG, "CDK FFI error creating melt quote: ${e.message}")
|
||||||
|
Result.failure(e)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to create melt quote", e)
|
Log.e(TAG, "Failed to create melt quote", e)
|
||||||
Result.failure(e)
|
Result.failure(e)
|
||||||
@@ -617,31 +436,6 @@ class CashuService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createDemoMeltQuote(invoice: String): MeltQuote {
|
|
||||||
// Extract amount from invoice (simplified)
|
|
||||||
val amount = if (invoice.contains("lnbc")) {
|
|
||||||
try {
|
|
||||||
// Simple parsing for demo
|
|
||||||
100L // Default amount
|
|
||||||
} catch (e: Exception) {
|
|
||||||
100L
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
100L
|
|
||||||
}
|
|
||||||
|
|
||||||
return MeltQuote(
|
|
||||||
id = "demo_melt_${System.currentTimeMillis()}",
|
|
||||||
amount = BigDecimal(amount),
|
|
||||||
feeReserve = BigDecimal(1), // 1 sat fee
|
|
||||||
unit = "sat",
|
|
||||||
request = invoice,
|
|
||||||
state = MeltQuoteState.UNPAID,
|
|
||||||
expiry = Date(System.currentTimeMillis() + 3600000), // 1 hour
|
|
||||||
paid = false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pay a Lightning invoice using ecash (melt)
|
* Pay a Lightning invoice using ecash (melt)
|
||||||
*/
|
*/
|
||||||
@@ -652,22 +446,24 @@ class CashuService {
|
|||||||
initializeWallet(DEFAULT_MINT_URL).getOrThrow()
|
initializeWallet(DEFAULT_MINT_URL).getOrThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
val success = if (isCdkAvailable && wallet != null) {
|
if (!isCdkAvailable || wallet == null) {
|
||||||
try {
|
return@withContext Result.failure(Exception("CDK not available"))
|
||||||
// Real payment would be implemented here
|
|
||||||
Log.d(TAG, "Would pay real invoice with quote: $quoteId")
|
|
||||||
true // Demo: simulate success
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to pay real invoice, using demo")
|
|
||||||
true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Demo: simulate successful payment
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Invoice payment result: $success")
|
Log.d(TAG, "Paying invoice with melt quote: $quoteId")
|
||||||
|
|
||||||
|
val meltResult = wallet!!.melt(quoteId)
|
||||||
|
|
||||||
|
Log.d(TAG, "Invoice payment result: ${meltResult.state}")
|
||||||
|
|
||||||
|
// FfiMelted.state is a String, check for success states
|
||||||
|
val success = meltResult.state.equals("paid", ignoreCase = true) ||
|
||||||
|
meltResult.state.equals("success", ignoreCase = true)
|
||||||
Result.success(success)
|
Result.success(success)
|
||||||
|
|
||||||
|
} catch (e: FfiException) {
|
||||||
|
Log.e(TAG, "CDK FFI error paying invoice: ${e.message}")
|
||||||
|
Result.failure(e)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to pay invoice", e)
|
Log.e(TAG, "Failed to pay invoice", e)
|
||||||
Result.failure(e)
|
Result.failure(e)
|
||||||
@@ -681,7 +477,7 @@ class CashuService {
|
|||||||
suspend fun decodeToken(token: String): Result<CashuToken> {
|
suspend fun decodeToken(token: String): Result<CashuToken> {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
// Simple token parsing for both real and demo modes
|
// For now, do simple parsing - CDK might have better token parsing functions
|
||||||
val amount = parseTokenAmount(token)
|
val amount = parseTokenAmount(token)
|
||||||
val memo = parseTokenMemo(token)
|
val memo = parseTokenMemo(token)
|
||||||
|
|
||||||
@@ -695,6 +491,7 @@ class CashuService {
|
|||||||
|
|
||||||
Log.d(TAG, "Decoded token: amount=$amount")
|
Log.d(TAG, "Decoded token: amount=$amount")
|
||||||
Result.success(cashuToken)
|
Result.success(cashuToken)
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to decode token", e)
|
Log.e(TAG, "Failed to decode token", e)
|
||||||
Result.failure(e)
|
Result.failure(e)
|
||||||
@@ -707,6 +504,8 @@ class CashuService {
|
|||||||
*/
|
*/
|
||||||
fun cleanup() {
|
fun cleanup() {
|
||||||
try {
|
try {
|
||||||
|
wallet?.destroy()
|
||||||
|
localStore?.destroy()
|
||||||
wallet = null
|
wallet = null
|
||||||
localStore = null
|
localStore = null
|
||||||
isInitialized = false
|
isInitialized = false
|
||||||
@@ -726,6 +525,16 @@ class CashuService {
|
|||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
|
|
||||||
|
private fun getApplicationContext(): Context {
|
||||||
|
return try {
|
||||||
|
val activityThread = Class.forName("android.app.ActivityThread")
|
||||||
|
val currentApplication = activityThread.getMethod("currentApplication")
|
||||||
|
currentApplication.invoke(null) as Context
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalStateException("Could not get application context", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun extractMintName(url: String): String {
|
private fun extractMintName(url: String): String {
|
||||||
return try {
|
return try {
|
||||||
val host = url.substringAfter("://").substringBefore("/")
|
val host = url.substringAfter("://").substringBefore("/")
|
||||||
@@ -749,7 +558,8 @@ class CashuService {
|
|||||||
}
|
}
|
||||||
token.startsWith("cashuA") -> {
|
token.startsWith("cashuA") -> {
|
||||||
// Real token parsing would be more complex
|
// Real token parsing would be more complex
|
||||||
1000L // Default for now
|
// For now, return default
|
||||||
|
1000L
|
||||||
}
|
}
|
||||||
else -> 1000L
|
else -> 1000L
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,8 +46,6 @@ fun WalletOverview(
|
|||||||
.background(Color.Black)
|
.background(Color.Black)
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
// CDK Status Banner
|
|
||||||
CdkStatusBanner(activeMint = activeMint)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
@@ -89,120 +87,6 @@ fun WalletOverview(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun CdkStatusBanner(activeMint: String?) {
|
|
||||||
val cashuService = CashuService.getInstance()
|
|
||||||
val isCdkAvailable = cashuService.isCdkAvailable()
|
|
||||||
|
|
||||||
Card(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
shape = RoundedCornerShape(8.dp),
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = if (isCdkAvailable) Color(0xFF001a0f) else Color(0xFF1a1a00)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(12.dp)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
// CDK Logo/Icon
|
|
||||||
Card(
|
|
||||||
shape = RoundedCornerShape(4.dp),
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = if (isCdkAvailable) Color(0xFF00C851) else Color(0xFFFFA500)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = if (isCdkAvailable) "CDK" else "DEMO",
|
|
||||||
color = Color.Black,
|
|
||||||
fontSize = 10.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
fontFamily = FontFamily.Monospace,
|
|
||||||
modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
|
|
||||||
Column {
|
|
||||||
Text(
|
|
||||||
text = if (isCdkAvailable) "✅ Real Cashu Wallet Active" else "⚠️ Demo Mode Active",
|
|
||||||
color = if (isCdkAvailable) Color(0xFF00C851) else Color(0xFFFFA500),
|
|
||||||
fontSize = 12.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
fontFamily = FontFamily.Monospace
|
|
||||||
)
|
|
||||||
|
|
||||||
activeMint?.let { mint ->
|
|
||||||
Text(
|
|
||||||
text = if (isCdkAvailable) "Connected to: ${extractMintName(mint)}"
|
|
||||||
else "Demo wallet: ${extractMintName(mint)}",
|
|
||||||
color = Color.Gray,
|
|
||||||
fontSize = 10.sp,
|
|
||||||
fontFamily = FontFamily.Monospace
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
|
||||||
|
|
||||||
// Connection status
|
|
||||||
Icon(
|
|
||||||
imageVector = if (isCdkAvailable) Icons.Filled.CheckCircle else Icons.Filled.Warning,
|
|
||||||
contentDescription = if (isCdkAvailable) "CDK Available" else "Demo Mode",
|
|
||||||
tint = if (isCdkAvailable) Color(0xFF00C851) else Color(0xFFFFA500),
|
|
||||||
modifier = Modifier.size(16.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
// Technical details
|
|
||||||
if (isCdkAvailable) {
|
|
||||||
Text(
|
|
||||||
text = "• Using Cashu Development Kit (CDK) FFI bindings",
|
|
||||||
color = Color.Gray,
|
|
||||||
fontSize = 9.sp,
|
|
||||||
fontFamily = FontFamily.Monospace
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = "• Real Bitcoin Lightning Network integration",
|
|
||||||
color = Color.Gray,
|
|
||||||
fontSize = 9.sp,
|
|
||||||
fontFamily = FontFamily.Monospace
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = "• Actual ecash minting and melting operations",
|
|
||||||
color = Color.Gray,
|
|
||||||
fontSize = 9.sp,
|
|
||||||
fontFamily = FontFamily.Monospace
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
text = "• CDK not available on this device architecture",
|
|
||||||
color = Color.Gray,
|
|
||||||
fontSize = 9.sp,
|
|
||||||
fontFamily = FontFamily.Monospace
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = "• Running in demonstration mode with simulated operations",
|
|
||||||
color = Color.Gray,
|
|
||||||
fontSize = 9.sp,
|
|
||||||
fontFamily = FontFamily.Monospace
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = "• No real Bitcoin transactions - for testing only",
|
|
||||||
color = Color.Gray,
|
|
||||||
fontSize = 9.sp,
|
|
||||||
fontFamily = FontFamily.Monospace
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun extractMintName(url: String): String {
|
private fun extractMintName(url: String): String {
|
||||||
return try {
|
return try {
|
||||||
val host = url.substringAfter("://").substringBefore("/")
|
val host = url.substringAfter("://").substringBefore("/")
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
if (activeMintUrl.isNullOrEmpty()) {
|
if (activeMintUrl.isNullOrEmpty()) {
|
||||||
Log.d(TAG, "No active mint, setting default")
|
Log.d(TAG, "No active mint, setting default")
|
||||||
// Use default mint URL from CashuService
|
// Use default mint URL from CashuService
|
||||||
cashuService.initializeWallet("https://mint.minibits.cash/Bitcoin").onSuccess {
|
cashuService.initializeWallet("https://testnut.cashu.space").onSuccess {
|
||||||
refreshBalance()
|
refreshBalance()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user