Improves the Redeeming feedback
Improves fee calculation from the mint
This commit is contained in:
@@ -18,8 +18,6 @@ data class CashuToken(
|
|||||||
val token: String,
|
val token: String,
|
||||||
val mint: String,
|
val mint: String,
|
||||||
val totalAmount: Long,
|
val totalAmount: Long,
|
||||||
val fees: Int,
|
|
||||||
val redeemInvoiceAmount: Long,
|
|
||||||
val proofs: JsonNode
|
val proofs: JsonNode
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,10 +36,8 @@ class CashuProcessor {
|
|||||||
for (proof in proofs) {
|
for (proof in proofs) {
|
||||||
totalAmount += proof.get("amount").asLong()
|
totalAmount += proof.get("amount").asLong()
|
||||||
}
|
}
|
||||||
val fees = Math.max(((totalAmount * 0.02).toInt()), 2)
|
|
||||||
val redeemInvoiceAmount = totalAmount - fees
|
|
||||||
|
|
||||||
return GenericLoadable.Loaded(CashuToken(cashuToken, mint, totalAmount, fees, redeemInvoiceAmount, proofs))
|
return GenericLoadable.Loaded(CashuToken(cashuToken, mint, totalAmount, proofs))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
return GenericLoadable.Error<CashuToken>("Could not parse this cashu token")
|
return GenericLoadable.Error<CashuToken>("Could not parse this cashu token")
|
||||||
}
|
}
|
||||||
@@ -53,10 +49,29 @@ class CashuProcessor {
|
|||||||
runCatching {
|
runCatching {
|
||||||
LightningAddressResolver().lnAddressInvoice(
|
LightningAddressResolver().lnAddressInvoice(
|
||||||
lnaddress = lud16,
|
lnaddress = lud16,
|
||||||
milliSats = token.redeemInvoiceAmount * 1000, // Make invoice and leave room for fees
|
milliSats = token.totalAmount * 1000, // Make invoice and leave room for fees
|
||||||
message = "Redeem Cashu",
|
message = "Calculate Fees for Cashu",
|
||||||
onSuccess = { invoice ->
|
onSuccess = { baseInvoice ->
|
||||||
meltInvoice(token, invoice, onSuccess, onError, context)
|
feeCalculator(
|
||||||
|
token.mint,
|
||||||
|
baseInvoice,
|
||||||
|
onSuccess = { fees ->
|
||||||
|
LightningAddressResolver().lnAddressInvoice(
|
||||||
|
lnaddress = lud16,
|
||||||
|
milliSats = (token.totalAmount - fees) * 1000, // Make invoice and leave room for fees
|
||||||
|
message = "Redeem Cashu",
|
||||||
|
onSuccess = { invoice ->
|
||||||
|
meltInvoice(token, invoice, fees, onSuccess, onError, context)
|
||||||
|
},
|
||||||
|
onProgress = {
|
||||||
|
},
|
||||||
|
onError = onError,
|
||||||
|
context = context
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onError = onError,
|
||||||
|
context
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onProgress = {
|
onProgress = {
|
||||||
},
|
},
|
||||||
@@ -66,7 +81,59 @@ class CashuProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun meltInvoice(token: CashuToken, invoice: String, onSuccess: (String, String) -> Unit, onError: (String, String) -> Unit, context: Context) {
|
fun feeCalculator(
|
||||||
|
mintAddress: String,
|
||||||
|
invoice: String,
|
||||||
|
onSuccess: (Int) -> Unit,
|
||||||
|
onError: (String, String) -> Unit,
|
||||||
|
context: Context
|
||||||
|
) {
|
||||||
|
checkNotInMainThread()
|
||||||
|
|
||||||
|
try {
|
||||||
|
val client = HttpClient.getHttpClient()
|
||||||
|
val url = "$mintAddress/checkfees" // Melt cashu tokens at Mint
|
||||||
|
|
||||||
|
val factory = Event.mapper.nodeFactory
|
||||||
|
|
||||||
|
val jsonObject = factory.objectNode()
|
||||||
|
jsonObject.put("pr", invoice)
|
||||||
|
|
||||||
|
val mediaType = "application/json; charset=utf-8".toMediaType()
|
||||||
|
val requestBody = jsonObject.toString().toRequestBody(mediaType)
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.post(requestBody)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
client.newCall(request).execute().use {
|
||||||
|
val body = it.body.string()
|
||||||
|
val tree = jacksonObjectMapper().readTree(body)
|
||||||
|
|
||||||
|
val feeCost = tree?.get("fee")?.asInt()
|
||||||
|
|
||||||
|
if (feeCost != null) {
|
||||||
|
onSuccess(
|
||||||
|
feeCost
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val msg = tree?.get("detail")?.asText()?.split('.')?.getOrNull(0)?.ifBlank { null }
|
||||||
|
onError(
|
||||||
|
context.getString(R.string.cashu_failed_redemption),
|
||||||
|
if (msg != null) {
|
||||||
|
context.getString(R.string.cashu_failed_redemption_explainer_error_msg, msg)
|
||||||
|
} else {
|
||||||
|
context.getString(R.string.cashu_failed_redemption_explainer_error_msg)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
onError(context.getString(R.string.cashu_sucessful_redemption), context.getString(R.string.cashu_failed_redemption_explainer_error_msg, e.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun meltInvoice(token: CashuToken, invoice: String, fees: Int, onSuccess: (String, String) -> Unit, onError: (String, String) -> Unit, context: Context) {
|
||||||
try {
|
try {
|
||||||
val client = HttpClient.getHttpClient()
|
val client = HttpClient.getHttpClient()
|
||||||
val url = token.mint + "/melt" // Melt cashu tokens at Mint
|
val url = token.mint + "/melt" // Melt cashu tokens at Mint
|
||||||
@@ -93,7 +160,7 @@ class CashuProcessor {
|
|||||||
if (successful) {
|
if (successful) {
|
||||||
onSuccess(
|
onSuccess(
|
||||||
context.getString(R.string.cashu_sucessful_redemption),
|
context.getString(R.string.cashu_sucessful_redemption),
|
||||||
context.getString(R.string.cashu_sucessful_redemption_explainer, token.totalAmount.toString(), token.fees.toString())
|
context.getString(R.string.cashu_sucessful_redemption_explainer, token.totalAmount.toString(), fees.toString())
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val msg = tree?.get("detail")?.asText()?.split('.')?.getOrNull(0)?.ifBlank { null }
|
val msg = tree?.get("detail")?.asText()?.split('.')?.getOrNull(0)?.ifBlank { null }
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import androidx.compose.animation.Crossfade
|
|||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
@@ -40,15 +41,19 @@ import androidx.core.content.ContextCompat.startActivity
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.service.CashuProcessor
|
import com.vitorpamplona.amethyst.service.CashuProcessor
|
||||||
import com.vitorpamplona.amethyst.service.CashuToken
|
import com.vitorpamplona.amethyst.service.CashuToken
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CashuPreview(cashutoken: String, accountViewModel: AccountViewModel) {
|
fun CashuPreview(cashutoken: String, accountViewModel: AccountViewModel) {
|
||||||
var cachuData by remember { mutableStateOf<GenericLoadable<CashuToken>>(GenericLoadable.Loading<CashuToken>()) }
|
var cachuData by remember {
|
||||||
|
mutableStateOf<GenericLoadable<CashuToken>>(GenericLoadable.Loading())
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(key1 = cashutoken) {
|
LaunchedEffect(key1 = cashutoken) {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
@@ -59,7 +64,7 @@ fun CashuPreview(cashutoken: String, accountViewModel: AccountViewModel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Crossfade(targetState = cachuData) {
|
Crossfade(targetState = cachuData, label = "CashuPreview(") {
|
||||||
when (it) {
|
when (it) {
|
||||||
is GenericLoadable.Loaded<CashuToken> -> CashuPreview(it.loaded, accountViewModel)
|
is GenericLoadable.Loaded<CashuToken> -> CashuPreview(it.loaded, accountViewModel)
|
||||||
is GenericLoadable.Error<CashuToken> -> Text(
|
is GenericLoadable.Error<CashuToken> -> Text(
|
||||||
@@ -85,14 +90,14 @@ fun CashuPreview(token: CashuToken, accountViewModel: AccountViewModel) {
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(start = 30.dp, end = 30.dp)
|
.padding(start = 20.dp, end = 20.dp, top = 10.dp, bottom = 10.dp)
|
||||||
.clip(shape = QuoteBorder)
|
.clip(shape = QuoteBorder)
|
||||||
.border(1.dp, MaterialTheme.colorScheme.subtleBorder, QuoteBorder)
|
.border(1.dp, MaterialTheme.colorScheme.subtleBorder, QuoteBorder)
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(30.dp)
|
.padding(20.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
@@ -117,37 +122,38 @@ fun CashuPreview(token: CashuToken, accountViewModel: AccountViewModel) {
|
|||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
token.totalAmount.let {
|
Text(
|
||||||
Text(
|
text = "${token.totalAmount} ${stringResource(id = R.string.sats)}",
|
||||||
text = "$it ${stringResource(id = R.string.sats)}",
|
fontSize = 25.sp,
|
||||||
fontSize = 25.sp,
|
fontWeight = FontWeight.W500,
|
||||||
fontWeight = FontWeight.W500,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 10.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(bottom = 10.dp)
|
.padding(vertical = 10.dp)
|
||||||
) {
|
)
|
||||||
Button(
|
|
||||||
|
|
||||||
modifier = Modifier
|
Row(
|
||||||
.padding(vertical = 10.dp).padding(horizontal = 2.dp),
|
modifier = Modifier
|
||||||
|
.padding(top = 5.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
var isRedeeming by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (lud16 != null) {
|
if (lud16 != null) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
|
isRedeeming = true
|
||||||
CashuProcessor().melt(
|
CashuProcessor().melt(
|
||||||
token,
|
token,
|
||||||
lud16,
|
lud16,
|
||||||
onSuccess = { title, message ->
|
onSuccess = { title, message ->
|
||||||
|
isRedeeming = false
|
||||||
accountViewModel.toast(title, message)
|
accountViewModel.toast(title, message)
|
||||||
},
|
},
|
||||||
onError = { title, message ->
|
onError = { title, message ->
|
||||||
|
isRedeeming = false
|
||||||
accountViewModel.toast(title, message)
|
accountViewModel.toast(title, message)
|
||||||
},
|
},
|
||||||
context
|
context
|
||||||
@@ -165,15 +171,22 @@ fun CashuPreview(token: CashuToken, accountViewModel: AccountViewModel) {
|
|||||||
containerColor = MaterialTheme.colorScheme.primary
|
containerColor = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
if (isRedeeming) {
|
||||||
|
LoadingAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = StdHorzSpacer)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.cashu_redeem),
|
stringResource(R.string.cashu_redeem),
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
fontSize = 18.sp
|
fontSize = 18.sp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = StdHorzSpacer)
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier
|
|
||||||
.padding(vertical = 10.dp).padding(horizontal = 1.dp),
|
|
||||||
onClick = {
|
onClick = {
|
||||||
if (useWebService) {
|
if (useWebService) {
|
||||||
// In case we want to use the cashu.me webservice
|
// In case we want to use the cashu.me webservice
|
||||||
|
|||||||
Reference in New Issue
Block a user