Merge branch 'main' into test/buzz-agent-support

This commit is contained in:
Vitor Pamplona
2026-07-26 23:02:48 -04:00
62 changed files with 1548 additions and 234 deletions
@@ -25,7 +25,12 @@ import com.vitorpamplona.amethyst.cli.Context
import com.vitorpamplona.amethyst.cli.DataDir
import com.vitorpamplona.amethyst.cli.Output
import com.vitorpamplona.amethyst.cli.commands.route
import com.vitorpamplona.amethyst.commons.cashu.ops.requireP2pkRedeemable
import com.vitorpamplona.quartz.lightning.LnInvoiceUtil
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nip60Cashu.p2pk.P2PKUnredeemableException
import com.vitorpamplona.quartz.nip60Cashu.p2pk.anyP2pkLocked
import com.vitorpamplona.quartz.nip60Cashu.token.CashuTokenB64Parser
/**
@@ -156,12 +161,31 @@ object CashuReceiveCommands {
Context.open(dataDir).use { ctx ->
ctx.prepare()
return try {
// Keys that can unlock a P2PK-locked token: the wallet's kind:17375
// key, plus — for a local key account — the identity key (some
// senders, e.g. Bey Wallet, P2PK-lock ecash to the recipient npub).
// Only decrypt the wallet key when a proof is actually locked (a
// signer round-trip on bunker accounts, wasted on a plain token).
val locked = parsed.any { it.proofs.anyP2pkLocked() }
val walletKey =
if (locked) ctx.cashuSnapshot().walletEvent?.let { runCatching { it.privkey(ctx.signer) }.getOrNull() } else null
val identityKey = if (locked) (ctx.signer as? NostrSignerInternal)?.keyPair?.privKey?.toHexKey() else null
// All-or-nothing: reject an unsignable P2PK lock before redeeming
// any group, so a multi-mint token never ends up half-redeemed.
if (locked) requireP2pkRedeemable(parsed.flatMap { it.proofs }, walletKey, identityKey)
var total = 0L
var lastTokenEventId: String? = null
var lastHistoryEventId: String? = null
var mint = ""
for (t in parsed) {
val redeemed = ctx.cashuOps().redeemToken(raw, t.proofs, t.mint)
val redeemed =
ctx.cashuOps().redeemToken(
cashuToken = raw,
proofs = t.proofs,
mintUrl = t.mint,
walletP2pkPrivkeyHex = walletKey,
identityPrivkeyHex = identityKey,
)
total += redeemed.amount
lastTokenEventId = redeemed.tokenEvent.id
lastHistoryEventId = redeemed.historyEvent.id
@@ -176,6 +200,8 @@ object CashuReceiveCommands {
),
)
0
} catch (e: P2PKUnredeemableException) {
Output.error("p2pk_locked", "token is P2PK-locked to a key this wallet can't sign for (${e.lockPubKeyHex})")
} catch (e: Exception) {
Output.error("mint_proofs_spent", describe(e))
}