feat: implement NIP-87 ecash mint discoverability
Add protocol support and UI rendering for NIP-87 which defines ecash mint discovery via three event kinds: MintRecommendationEvent (38000), CashuMintEvent (38172), and FedimintEvent (38173). https://claude.ai/code/session_01JR2nFVPjPGG9jTV4Qq2PUW
This commit is contained in:
@@ -114,6 +114,7 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderAudioTrack
|
|||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderBadgeAward
|
import com.vitorpamplona.amethyst.ui.note.types.RenderBadgeAward
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderCalendarDateSlotEvent
|
import com.vitorpamplona.amethyst.ui.note.types.RenderCalendarDateSlotEvent
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderCalendarTimeSlotEvent
|
import com.vitorpamplona.amethyst.ui.note.types.RenderCalendarTimeSlotEvent
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.types.RenderCashuMint
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderChannelMessage
|
import com.vitorpamplona.amethyst.ui.note.types.RenderChannelMessage
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderChatMessage
|
import com.vitorpamplona.amethyst.ui.note.types.RenderChatMessage
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderChatMessageEncryptedFile
|
import com.vitorpamplona.amethyst.ui.note.types.RenderChatMessageEncryptedFile
|
||||||
@@ -122,6 +123,7 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderClassifieds
|
|||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderCodeSnippetEvent
|
import com.vitorpamplona.amethyst.ui.note.types.RenderCodeSnippetEvent
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderCommunity
|
import com.vitorpamplona.amethyst.ui.note.types.RenderCommunity
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderEmojiPack
|
import com.vitorpamplona.amethyst.ui.note.types.RenderEmojiPack
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.types.RenderFedimint
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderFhirResource
|
import com.vitorpamplona.amethyst.ui.note.types.RenderFhirResource
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderGitIssueEvent
|
import com.vitorpamplona.amethyst.ui.note.types.RenderGitIssueEvent
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderGitPatchEvent
|
import com.vitorpamplona.amethyst.ui.note.types.RenderGitPatchEvent
|
||||||
@@ -135,6 +137,7 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderLiveChessChallenge
|
|||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderLiveChessGameEnd
|
import com.vitorpamplona.amethyst.ui.note.types.RenderLiveChessGameEnd
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderLnZap
|
import com.vitorpamplona.amethyst.ui.note.types.RenderLnZap
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderLongFormContent
|
import com.vitorpamplona.amethyst.ui.note.types.RenderLongFormContent
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.types.RenderMintRecommendation
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderNIP90ContentDiscoveryResponse
|
import com.vitorpamplona.amethyst.ui.note.types.RenderNIP90ContentDiscoveryResponse
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderNIP90Status
|
import com.vitorpamplona.amethyst.ui.note.types.RenderNIP90Status
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderNipContent
|
import com.vitorpamplona.amethyst.ui.note.types.RenderNipContent
|
||||||
@@ -267,6 +270,9 @@ import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefiniti
|
|||||||
import com.vitorpamplona.quartz.nip72ModCommunities.isACommunityPost
|
import com.vitorpamplona.quartz.nip72ModCommunities.isACommunityPost
|
||||||
import com.vitorpamplona.quartz.nip75ZapGoals.GoalEvent
|
import com.vitorpamplona.quartz.nip75ZapGoals.GoalEvent
|
||||||
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.cashu.CashuMintEvent
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.fedimint.FedimintEvent
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.recommendation.MintRecommendationEvent
|
||||||
import com.vitorpamplona.quartz.nip88Polls.poll.PollEvent
|
import com.vitorpamplona.quartz.nip88Polls.poll.PollEvent
|
||||||
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
|
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
|
||||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
|
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
|
||||||
@@ -1052,6 +1058,18 @@ private fun RenderNoteRow(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is CashuMintEvent -> {
|
||||||
|
RenderCashuMint(noteEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
is FedimintEvent -> {
|
||||||
|
RenderFedimint(noteEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
is MintRecommendationEvent -> {
|
||||||
|
RenderMintRecommendation(noteEvent)
|
||||||
|
}
|
||||||
|
|
||||||
is ClassifiedsEvent -> {
|
is ClassifiedsEvent -> {
|
||||||
RenderClassifieds(
|
RenderClassifieds(
|
||||||
noteEvent,
|
noteEvent,
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.note.types
|
||||||
|
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.SmallBorder
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.cashu.CashuMintEvent
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.fedimint.FedimintEvent
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.recommendation.MintRecommendationEvent
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RenderCashuMint(noteEvent: CashuMintEvent) {
|
||||||
|
val mintUrl = remember(noteEvent) { noteEvent.mintUrl() }
|
||||||
|
val nuts = remember(noteEvent) { noteEvent.nuts() }
|
||||||
|
val network = remember(noteEvent) { noteEvent.network() }
|
||||||
|
|
||||||
|
MintAnnouncementCard(
|
||||||
|
title = "Cashu Mint",
|
||||||
|
url = mintUrl,
|
||||||
|
network = network.code,
|
||||||
|
capabilities = nuts,
|
||||||
|
capabilitiesLabel = "NUTs",
|
||||||
|
metadata = noteEvent.content.ifBlank { null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RenderFedimint(noteEvent: FedimintEvent) {
|
||||||
|
val inviteCodes = remember(noteEvent) { noteEvent.inviteCodes() }
|
||||||
|
val modules = remember(noteEvent) { noteEvent.modules() }
|
||||||
|
val network = remember(noteEvent) { noteEvent.network() }
|
||||||
|
|
||||||
|
MintAnnouncementCard(
|
||||||
|
title = "Fedimint",
|
||||||
|
url = inviteCodes.firstOrNull(),
|
||||||
|
network = network.code,
|
||||||
|
capabilities = modules,
|
||||||
|
capabilitiesLabel = "Modules",
|
||||||
|
metadata = noteEvent.content.ifBlank { null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RenderMintRecommendation(noteEvent: MintRecommendationEvent) {
|
||||||
|
val mintUrls = remember(noteEvent) { noteEvent.mintUrls() }
|
||||||
|
val mintType =
|
||||||
|
remember(noteEvent) {
|
||||||
|
when {
|
||||||
|
noteEvent.isCashuRecommendation() -> "Cashu Mint"
|
||||||
|
noteEvent.isFedimintRecommendation() -> "Fedimint"
|
||||||
|
else -> "Ecash Mint"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MintAnnouncementCard(
|
||||||
|
title = "$mintType Recommendation",
|
||||||
|
url = mintUrls.firstOrNull(),
|
||||||
|
network = null,
|
||||||
|
capabilities = emptyList(),
|
||||||
|
capabilitiesLabel = null,
|
||||||
|
metadata = noteEvent.content.ifBlank { null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun MintAnnouncementCard(
|
||||||
|
title: String,
|
||||||
|
url: String?,
|
||||||
|
network: String?,
|
||||||
|
capabilities: List<String>,
|
||||||
|
capabilitiesLabel: String?,
|
||||||
|
metadata: String?,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.clip(shape = QuoteBorder)
|
||||||
|
.border(
|
||||||
|
1.dp,
|
||||||
|
MaterialTheme.colorScheme.subtleBorder,
|
||||||
|
QuoteBorder,
|
||||||
|
).fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(10.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (network != null) {
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
text = network,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.clip(SmallBorder)
|
||||||
|
.border(1.dp, MaterialTheme.colorScheme.primary, SmallBorder)
|
||||||
|
.padding(horizontal = 6.dp, vertical = 2.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
url?.let {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier.padding(top = 4.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (capabilities.isNotEmpty() && capabilitiesLabel != null) {
|
||||||
|
FlowRow(
|
||||||
|
modifier = Modifier.padding(top = 6.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "$capabilitiesLabel: ",
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = Color.Gray,
|
||||||
|
)
|
||||||
|
capabilities.forEach { capability ->
|
||||||
|
Text(
|
||||||
|
text = capability,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = Color.Gray,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.clip(SmallBorder)
|
||||||
|
.border(1.dp, Color.Gray.copy(alpha = 0.3f), SmallBorder)
|
||||||
|
.padding(horizontal = 4.dp, vertical = 1.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata?.let {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray,
|
||||||
|
maxLines = 3,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier.padding(top = 6.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -147,10 +147,12 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderAttestorProficiency
|
|||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderAttestorRecommendation
|
import com.vitorpamplona.amethyst.ui.note.types.RenderAttestorRecommendation
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderCalendarDateSlotEvent
|
import com.vitorpamplona.amethyst.ui.note.types.RenderCalendarDateSlotEvent
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderCalendarTimeSlotEvent
|
import com.vitorpamplona.amethyst.ui.note.types.RenderCalendarTimeSlotEvent
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.types.RenderCashuMint
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderChannelMessage
|
import com.vitorpamplona.amethyst.ui.note.types.RenderChannelMessage
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderChatMessageEncryptedFile
|
import com.vitorpamplona.amethyst.ui.note.types.RenderChatMessageEncryptedFile
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderCodeSnippetHeaderForThread
|
import com.vitorpamplona.amethyst.ui.note.types.RenderCodeSnippetHeaderForThread
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderEmojiPack
|
import com.vitorpamplona.amethyst.ui.note.types.RenderEmojiPack
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.types.RenderFedimint
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderFhirResource
|
import com.vitorpamplona.amethyst.ui.note.types.RenderFhirResource
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderGitIssueEvent
|
import com.vitorpamplona.amethyst.ui.note.types.RenderGitIssueEvent
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderGitPatchEvent
|
import com.vitorpamplona.amethyst.ui.note.types.RenderGitPatchEvent
|
||||||
@@ -160,6 +162,7 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderHighlight
|
|||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderInteractiveStory
|
import com.vitorpamplona.amethyst.ui.note.types.RenderInteractiveStory
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderLiveActivityChatMessage
|
import com.vitorpamplona.amethyst.ui.note.types.RenderLiveActivityChatMessage
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderLnZap
|
import com.vitorpamplona.amethyst.ui.note.types.RenderLnZap
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.types.RenderMintRecommendation
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderPinListEvent
|
import com.vitorpamplona.amethyst.ui.note.types.RenderPinListEvent
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderPoll
|
import com.vitorpamplona.amethyst.ui.note.types.RenderPoll
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderPostApproval
|
import com.vitorpamplona.amethyst.ui.note.types.RenderPostApproval
|
||||||
@@ -268,6 +271,9 @@ import com.vitorpamplona.quartz.nip72ModCommunities.communityAddress
|
|||||||
import com.vitorpamplona.quartz.nip72ModCommunities.isACommunityPost
|
import com.vitorpamplona.quartz.nip72ModCommunities.isACommunityPost
|
||||||
import com.vitorpamplona.quartz.nip75ZapGoals.GoalEvent
|
import com.vitorpamplona.quartz.nip75ZapGoals.GoalEvent
|
||||||
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.cashu.CashuMintEvent
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.fedimint.FedimintEvent
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.recommendation.MintRecommendationEvent
|
||||||
import com.vitorpamplona.quartz.nip88Polls.poll.PollEvent
|
import com.vitorpamplona.quartz.nip88Polls.poll.PollEvent
|
||||||
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
|
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
|
||||||
import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent
|
import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent
|
||||||
@@ -753,6 +759,12 @@ private fun FullBleedNoteCompose(
|
|||||||
accountViewModel,
|
accountViewModel,
|
||||||
nav,
|
nav,
|
||||||
)
|
)
|
||||||
|
} else if (noteEvent is CashuMintEvent) {
|
||||||
|
RenderCashuMint(noteEvent)
|
||||||
|
} else if (noteEvent is FedimintEvent) {
|
||||||
|
RenderFedimint(noteEvent)
|
||||||
|
} else if (noteEvent is MintRecommendationEvent) {
|
||||||
|
RenderMintRecommendation(noteEvent)
|
||||||
} else if (noteEvent is PollEvent) {
|
} else if (noteEvent is PollEvent) {
|
||||||
RenderPoll(
|
RenderPoll(
|
||||||
note = baseNote,
|
note = baseNote,
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip87Ecash
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
|
class MintUrlTag {
|
||||||
|
companion object {
|
||||||
|
const val TAG_NAME = "u"
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): String? {
|
||||||
|
ensure(tag.has(1)) { return null }
|
||||||
|
ensure(tag[0] == TAG_NAME) { return null }
|
||||||
|
ensure(tag[1].isNotEmpty()) { return null }
|
||||||
|
return tag[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(url: String) = arrayOf(TAG_NAME, url)
|
||||||
|
|
||||||
|
fun assemble(urls: List<String>) = urls.map { assemble(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip87Ecash
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
|
enum class NetworkType(
|
||||||
|
val code: String,
|
||||||
|
) {
|
||||||
|
MAINNET("mainnet"),
|
||||||
|
TESTNET("testnet"),
|
||||||
|
SIGNET("signet"),
|
||||||
|
REGTEST("regtest"),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromCode(code: String) = entries.firstOrNull { it.code == code }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetworkTag {
|
||||||
|
companion object {
|
||||||
|
const val TAG_NAME = "n"
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): NetworkType? {
|
||||||
|
ensure(tag.has(1)) { return null }
|
||||||
|
ensure(tag[0] == TAG_NAME) { return null }
|
||||||
|
ensure(tag[1].isNotEmpty()) { return null }
|
||||||
|
return NetworkType.fromCode(tag[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(network: NetworkType) = arrayOf(TAG_NAME, network.code)
|
||||||
|
}
|
||||||
|
}
|
||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip87Ecash.cashu
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
||||||
|
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.NetworkType
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
class CashuMintEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: Array<Array<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey,
|
||||||
|
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||||
|
fun mintUrl() = tags.mintUrl()
|
||||||
|
|
||||||
|
fun nuts() = tags.nuts()
|
||||||
|
|
||||||
|
fun network() = tags.network()
|
||||||
|
|
||||||
|
fun dTag() = tags.dTag()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KIND = 38172
|
||||||
|
const val ALT_DESCRIPTION = "Cashu Mint Announcement"
|
||||||
|
|
||||||
|
fun build(
|
||||||
|
mintPubKey: String,
|
||||||
|
mintUrl: String,
|
||||||
|
nuts: List<String>,
|
||||||
|
network: NetworkType = NetworkType.MAINNET,
|
||||||
|
metadata: String = "",
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
initializer: TagArrayBuilder<CashuMintEvent>.() -> Unit = {},
|
||||||
|
) = eventTemplate(KIND, metadata, createdAt) {
|
||||||
|
alt(ALT_DESCRIPTION)
|
||||||
|
dTag(mintPubKey)
|
||||||
|
mintUrl(mintUrl)
|
||||||
|
nuts(nuts)
|
||||||
|
network(network)
|
||||||
|
initializer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip87Ecash.cashu
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.MintUrlTag
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.NetworkTag
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.NetworkType
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.cashu.tags.NutsTag
|
||||||
|
|
||||||
|
fun TagArrayBuilder<CashuMintEvent>.dTag(mintPubKey: String) = addUnique(arrayOf("d", mintPubKey))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<CashuMintEvent>.mintUrl(url: String) = addUnique(MintUrlTag.assemble(url))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<CashuMintEvent>.nuts(nuts: List<String>) = add(NutsTag.assemble(nuts))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<CashuMintEvent>.network(network: NetworkType) = addUnique(NetworkTag.assemble(network))
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip87Ecash.cashu
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.MintUrlTag
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.NetworkTag
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.NetworkType
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.cashu.tags.NutsTag
|
||||||
|
|
||||||
|
fun TagArray.mintUrl(): String? = firstNotNullOfOrNull(MintUrlTag::parse)
|
||||||
|
|
||||||
|
fun TagArray.nuts(): List<String> = firstNotNullOfOrNull(NutsTag::parse) ?: emptyList()
|
||||||
|
|
||||||
|
fun TagArray.network(): NetworkType = firstNotNullOfOrNull(NetworkTag::parse) ?: NetworkType.MAINNET
|
||||||
|
|
||||||
|
fun TagArray.dTag(): String? = firstOrNull { it.size >= 2 && it[0] == "d" }?.get(1)
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip87Ecash.cashu.tags
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
|
class NutsTag {
|
||||||
|
companion object {
|
||||||
|
const val TAG_NAME = "nuts"
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): List<String>? {
|
||||||
|
ensure(tag.has(1)) { return null }
|
||||||
|
ensure(tag[0] == TAG_NAME) { return null }
|
||||||
|
ensure(tag[1].isNotEmpty()) { return null }
|
||||||
|
return tag.drop(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(nuts: List<String>) = arrayOf(TAG_NAME, *nuts.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip87Ecash.fedimint
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
||||||
|
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.NetworkType
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
class FedimintEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: Array<Array<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey,
|
||||||
|
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||||
|
fun inviteCodes() = tags.inviteCodes()
|
||||||
|
|
||||||
|
fun modules() = tags.modules()
|
||||||
|
|
||||||
|
fun network() = tags.network()
|
||||||
|
|
||||||
|
fun dTag() = tags.dTag()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KIND = 38173
|
||||||
|
const val ALT_DESCRIPTION = "Fedimint Announcement"
|
||||||
|
|
||||||
|
fun build(
|
||||||
|
federationId: String,
|
||||||
|
inviteCodes: List<String>,
|
||||||
|
modules: List<String>,
|
||||||
|
network: NetworkType = NetworkType.MAINNET,
|
||||||
|
metadata: String = "",
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
initializer: TagArrayBuilder<FedimintEvent>.() -> Unit = {},
|
||||||
|
) = eventTemplate(KIND, metadata, createdAt) {
|
||||||
|
alt(ALT_DESCRIPTION)
|
||||||
|
dTag(federationId)
|
||||||
|
inviteCodes(inviteCodes)
|
||||||
|
modules(modules)
|
||||||
|
network(network)
|
||||||
|
initializer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip87Ecash.fedimint
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.MintUrlTag
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.NetworkTag
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.NetworkType
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.fedimint.tags.ModulesTag
|
||||||
|
|
||||||
|
fun TagArrayBuilder<FedimintEvent>.dTag(federationId: String) = addUnique(arrayOf("d", federationId))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<FedimintEvent>.inviteCodes(codes: List<String>) = addAll(MintUrlTag.assemble(codes))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<FedimintEvent>.modules(modules: List<String>) = add(ModulesTag.assemble(modules))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<FedimintEvent>.network(network: NetworkType) = addUnique(NetworkTag.assemble(network))
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip87Ecash.fedimint
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.MintUrlTag
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.NetworkTag
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.NetworkType
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.fedimint.tags.ModulesTag
|
||||||
|
|
||||||
|
fun TagArray.inviteCodes(): List<String> = mapNotNull(MintUrlTag::parse)
|
||||||
|
|
||||||
|
fun TagArray.modules(): List<String> = firstNotNullOfOrNull(ModulesTag::parse) ?: emptyList()
|
||||||
|
|
||||||
|
fun TagArray.network(): NetworkType = firstNotNullOfOrNull(NetworkTag::parse) ?: NetworkType.MAINNET
|
||||||
|
|
||||||
|
fun TagArray.dTag(): String? = firstOrNull { it.size >= 2 && it[0] == "d" }?.get(1)
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip87Ecash.fedimint.tags
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
|
class ModulesTag {
|
||||||
|
companion object {
|
||||||
|
const val TAG_NAME = "modules"
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): List<String>? {
|
||||||
|
ensure(tag.has(1)) { return null }
|
||||||
|
ensure(tag[0] == TAG_NAME) { return null }
|
||||||
|
ensure(tag[1].isNotEmpty()) { return null }
|
||||||
|
return tag.drop(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(modules: List<String>) = arrayOf(TAG_NAME, *modules.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip87Ecash.recommendation
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
||||||
|
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.cashu.CashuMintEvent
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.fedimint.FedimintEvent
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
class MintRecommendationEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: Array<Array<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey,
|
||||||
|
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||||
|
fun mintUrls() = tags.mintUrls()
|
||||||
|
|
||||||
|
fun mintEventKind() = tags.mintEventKind()
|
||||||
|
|
||||||
|
fun dTag() = tags.dTag()
|
||||||
|
|
||||||
|
fun mintEventAddresses() = tags.mintEventAddresses()
|
||||||
|
|
||||||
|
fun isCashuRecommendation() = mintEventKind() == CashuMintEvent.KIND
|
||||||
|
|
||||||
|
fun isFedimintRecommendation() = mintEventKind() == FedimintEvent.KIND
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KIND = 38000
|
||||||
|
const val ALT_DESCRIPTION = "Mint Recommendation"
|
||||||
|
|
||||||
|
fun build(
|
||||||
|
mintIdentifier: String,
|
||||||
|
mintKind: Int,
|
||||||
|
review: String = "",
|
||||||
|
mintUrls: List<String> = emptyList(),
|
||||||
|
mintEventAddress: String? = null,
|
||||||
|
mintEventRelay: String? = null,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
initializer: TagArrayBuilder<MintRecommendationEvent>.() -> Unit = {},
|
||||||
|
) = eventTemplate(KIND, review, createdAt) {
|
||||||
|
alt(ALT_DESCRIPTION)
|
||||||
|
dTag(mintIdentifier)
|
||||||
|
kTag(mintKind)
|
||||||
|
mintUrls.forEach { mintUrl(it) }
|
||||||
|
if (mintEventAddress != null) {
|
||||||
|
aTag(mintEventAddress, mintEventRelay)
|
||||||
|
}
|
||||||
|
initializer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip87Ecash.recommendation
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.MintUrlTag
|
||||||
|
|
||||||
|
fun TagArrayBuilder<MintRecommendationEvent>.dTag(mintIdentifier: String) = addUnique(arrayOf("d", mintIdentifier))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<MintRecommendationEvent>.kTag(kind: Int) = addUnique(arrayOf("k", kind.toString()))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<MintRecommendationEvent>.mintUrl(url: String) = add(MintUrlTag.assemble(url))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<MintRecommendationEvent>.aTag(
|
||||||
|
address: String,
|
||||||
|
relay: String? = null,
|
||||||
|
) = if (relay != null) {
|
||||||
|
add(arrayOf("a", address, relay))
|
||||||
|
} else {
|
||||||
|
add(arrayOf("a", address))
|
||||||
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip87Ecash.recommendation
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.MintUrlTag
|
||||||
|
|
||||||
|
fun TagArray.mintUrls(): List<String> = mapNotNull(MintUrlTag::parse)
|
||||||
|
|
||||||
|
fun TagArray.mintEventKind(): Int? = firstOrNull { it.size >= 2 && it[0] == "k" }?.get(1)?.toIntOrNull()
|
||||||
|
|
||||||
|
fun TagArray.dTag(): String? = firstOrNull { it.size >= 2 && it[0] == "d" }?.get(1)
|
||||||
|
|
||||||
|
fun TagArray.mintEventAddresses(): List<String> = filter { it.size >= 2 && it[0] == "a" }.map { it[1] }
|
||||||
@@ -160,6 +160,9 @@ import com.vitorpamplona.quartz.nip85TrustedAssertions.events.EventAssertionEven
|
|||||||
import com.vitorpamplona.quartz.nip85TrustedAssertions.externalIds.ExternalIdAssertionEvent
|
import com.vitorpamplona.quartz.nip85TrustedAssertions.externalIds.ExternalIdAssertionEvent
|
||||||
import com.vitorpamplona.quartz.nip85TrustedAssertions.list.TrustProviderListEvent
|
import com.vitorpamplona.quartz.nip85TrustedAssertions.list.TrustProviderListEvent
|
||||||
import com.vitorpamplona.quartz.nip85TrustedAssertions.users.ContactCardEvent
|
import com.vitorpamplona.quartz.nip85TrustedAssertions.users.ContactCardEvent
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.cashu.CashuMintEvent
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.fedimint.FedimintEvent
|
||||||
|
import com.vitorpamplona.quartz.nip87Ecash.recommendation.MintRecommendationEvent
|
||||||
import com.vitorpamplona.quartz.nip88Polls.poll.PollEvent
|
import com.vitorpamplona.quartz.nip88Polls.poll.PollEvent
|
||||||
import com.vitorpamplona.quartz.nip88Polls.response.PollResponseEvent
|
import com.vitorpamplona.quartz.nip88Polls.response.PollResponseEvent
|
||||||
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
|
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
|
||||||
@@ -228,6 +231,7 @@ class EventFactory {
|
|||||||
CalendarEvent.KIND -> CalendarEvent(id, pubKey, createdAt, tags, content, sig)
|
CalendarEvent.KIND -> CalendarEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
CalendarTimeSlotEvent.KIND -> CalendarTimeSlotEvent(id, pubKey, createdAt, tags, content, sig)
|
CalendarTimeSlotEvent.KIND -> CalendarTimeSlotEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
CalendarRSVPEvent.KIND -> CalendarRSVPEvent(id, pubKey, createdAt, tags, content, sig)
|
CalendarRSVPEvent.KIND -> CalendarRSVPEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
CashuMintEvent.KIND -> CashuMintEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
CashuMintQuoteEvent.KIND -> CashuMintQuoteEvent(id, pubKey, createdAt, tags, content, sig)
|
CashuMintQuoteEvent.KIND -> CashuMintQuoteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
CashuTokenEvent.KIND -> CashuTokenEvent(id, pubKey, createdAt, tags, content, sig)
|
CashuTokenEvent.KIND -> CashuTokenEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
CashuSpendingHistoryEvent.KIND -> CashuSpendingHistoryEvent(id, pubKey, createdAt, tags, content, sig)
|
CashuSpendingHistoryEvent.KIND -> CashuSpendingHistoryEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
@@ -263,6 +267,7 @@ class EventFactory {
|
|||||||
EphemeralChatEvent.KIND -> EphemeralChatEvent(id, pubKey, createdAt, tags, content, sig)
|
EphemeralChatEvent.KIND -> EphemeralChatEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
EphemeralChatListEvent.KIND -> EphemeralChatListEvent(id, pubKey, createdAt, tags, content, sig)
|
EphemeralChatListEvent.KIND -> EphemeralChatListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
ExternalIdentitiesEvent.KIND -> ExternalIdentitiesEvent(id, pubKey, createdAt, tags, content, sig)
|
ExternalIdentitiesEvent.KIND -> ExternalIdentitiesEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
FedimintEvent.KIND -> FedimintEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
FileHeaderEvent.KIND -> FileHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
FileHeaderEvent.KIND -> FileHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
ProfileGalleryEntryEvent.KIND -> ProfileGalleryEntryEvent(id, pubKey, createdAt, tags, content, sig)
|
ProfileGalleryEntryEvent.KIND -> ProfileGalleryEntryEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
FileServersEvent.KIND -> FileServersEvent(id, pubKey, createdAt, tags, content, sig)
|
FileServersEvent.KIND -> FileServersEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
@@ -301,6 +306,7 @@ class EventFactory {
|
|||||||
MeetingRoomEvent.KIND -> MeetingRoomEvent(id, pubKey, createdAt, tags, content, sig)
|
MeetingRoomEvent.KIND -> MeetingRoomEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
MeetingRoomPresenceEvent.KIND -> MeetingRoomPresenceEvent(id, pubKey, createdAt, tags, content, sig)
|
MeetingRoomPresenceEvent.KIND -> MeetingRoomPresenceEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
MeetingSpaceEvent.KIND -> MeetingSpaceEvent(id, pubKey, createdAt, tags, content, sig)
|
MeetingSpaceEvent.KIND -> MeetingSpaceEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
MintRecommendationEvent.KIND -> MintRecommendationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
MetadataEvent.KIND -> MetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
MetadataEvent.KIND -> MetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
MuteListEvent.KIND -> MuteListEvent(id, pubKey, createdAt, tags, content, sig)
|
MuteListEvent.KIND -> MuteListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
NNSEvent.KIND -> NNSEvent(id, pubKey, createdAt, tags, content, sig)
|
NNSEvent.KIND -> NNSEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|||||||
Reference in New Issue
Block a user