
Auth Signers
FreeSimplify event signing with flexible signer options.
Free · Opens the source repo
What Auth Signers does
Auth Signers provides a structured approach to event signing within the Amethyst framework by offering three distinct signer types: local, remote, and external. Each signer implements the same abstract contract, allowing developers to integrate signing functionality without worrying about the underlying implementation. This abstraction is particularly useful for applications that need to publish signed Nostr events, such as follows, posts, and reactions. By using the appropriate signer, developers can ensure that their application meets the desired security and user experience requirements.
The skill includes detailed instructions on how to incorporate a new signing flow, whether it involves a local keypair, a remote NIP-46 bunker signer, or an external NIP-55 Android signer. It also addresses common issues that developers might encounter, such as debugging sign request timeouts or onboarding new signer types. The provided SignerResult contract helps manage the outcomes of signing operations, ensuring that developers can handle success, timeouts, and user-denied scenarios effectively.
Auth Signers is designed for developers working with the Amethyst framework who need to implement or modify event signing features. It provides clear guidelines on how to choose the appropriate signer based on the application's requirements and the user's preferences. By leveraging this skill, developers can streamline the process of signing events, leading to a more efficient development cycle and improved application performance.
When to use it
Use this skill when adding new features that require event signing or when debugging signing operations in Amethyst.
When not to use it
This skill may not be suitable for applications that do not require event signing or for developers unfamiliar with the Amethyst framework.
What you can build with it
Publishing a Follow Event
When implementing a feature that allows users to follow others, use the `NostrSigner` to sign the follow event securely.
Debugging Sign Requests
If users report issues with signing requests, utilize the skill to troubleshoot timeouts and connection issues with remote signers.
Onboarding New Signer Types
When adding support for new signer types, refer to the skill for guidance on integrating hardware signers or browser extensions.
How to install Auth Signers
View source1. Install with the skills CLI
npx skills add vitorpamplona/amethyst/auth-signers --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by vitorpamplonaAuth & Signers
Any time Amethyst produces a signed Nostr event, it goes through a NostrSigner. There are three kinds; all three implement the same abstract contract so feature code doesn't care which one the user has configured.
When to Use This Skill
- Adding a new flow that publishes an event (follow, post, react, zap, profile edit).
- Reviewing whether a feature works when the user has a remote bunker signer or an external Android signer.
- Debugging "Sign request approved but nothing happens" / timeouts on sign operations.
- Onboarding a new signer kind (hardware signer, browser extension, etc.).
- Understanding the NIP-46 bunker request/response taxonomy.
The Abstract Contract
quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/NostrSigner.kt:
abstract class NostrSigner(val pubKey: HexKey) {
abstract fun <T : Event> sign(
template: EventTemplate<T>,
onReady: (T) -> Unit,
)
abstract fun nip04Encrypt(plaintext: String, toPubKey: HexKey, onReady: (String) -> Unit)
abstract fun nip04Decrypt(ciphertext: String, fromPubKey: HexKey, onReady: (String) -> Unit)
abstract fun nip44Encrypt(...)
abstract fun nip44Decrypt(...)
abstract fun decryptZapEvent(event: LnZapRequestEvent, onReady: (LnZapRequestEvent) -> Unit)
}
Sibling files in the same folder:
NostrSignerInternal.kt— in-process signer with the user's seckey in memory. Fastest; used for locally-stored accounts.NostrSignerSync.kt— blocking wrapper for scripts / migrations / tests where callbacks are inconvenient.EventTemplate.kt— the unsigned holder passed tosign().SignerExceptions.kt— the error taxonomy (user denied, timeout, unsupported method, etc.).caches/— request cache so duplicate sign/encrypt requests coalesce.
Concrete implementations
- Local (in-process):
NostrSignerInternal— directSecp256k1Instance.signSchnorr+ NIP-44 inline. Used by accounts created/imported into Amethyst. - Remote (NIP-46 bunker):
quartz/.../nip46RemoteSigner/signer/NostrSignerRemote.kt. Talks to a bunker service over Nostr DMs using theBunkerRequest*/BunkerResponse*event taxonomy (BunkerRequestConnect,BunkerRequestSign,BunkerRequestNip44Encrypt, …). - Android external (NIP-55):
quartz/src/androidMain/.../nip55AndroidSigner/client/NostrSignerExternal.kt. Uses Android intents + content provider to delegate to another app on the same device. Launcher:ExternalSignerLogin.kt,IActivityLauncher.kt. Install-check:IsExternalSignerInstalled.kt.
The SignerResult Contract
Signers return via callback (and internally track via SignerResult sealed types in nip46RemoteSigner/signer/SignerResult.kt and nip55AndroidSigner/api/SignerResult.kt). Result variants cover success, user-denied, timeout, remote-disconnected, unsupported. Feature code should:
- Pass a callback that handles success.
- Trust the cache/timeout behavior — don't roll your own retry.
- Surface
SignerExceptionsto the user with actionable messaging (e.g. "Bunker disconnected — reconnect?").
Typical Flow (Feature Code)
// High-level: Account methods already do this internally.
val signer: NostrSigner = account.signer // whichever kind the user configured
val template = reactionEventTemplate(noteId, authorPubKey, "+")
signer.sign(template) { signed ->
account.sendToRelays(signed) // or similar pipeline
}
Most feature code should go through Account's mutation methods (account.sendReaction, account.follow) rather than touching the signer directly — the account layer handles signing + publishing + local state update atomically. Reach for the signer directly only when Account doesn't have a helper.
Choosing a Signer at Sign-Up
Entry points:
- Existing private key (
nsec, 32-byte hex, file) →NostrSignerInternal. - Bunker URL (
bunker://...) →NostrSignerRemote.fromBunkerUri(bunkerUri, localSigner, client)innip46RemoteSigner/signer/NostrSignerRemote.ktparses the URI and returns aNostrSignerRemote; then call itssuspend fun connect()to perform the NIP-46 handshake. - Installed external signer app (Amber, nos2x, etc. on Android) →
ExternalSignerLogin.launch(...)opens the signer app; approval yields aNostrSignerExternal.
The UI hosts both flows via amethyst/.../ui/screen/loggedOff/login/ — look there for ExternalSignerButton.kt and the bunker-URL paste screen.
Trade-offs
| Signer | Latency | Offline OK? | Security | UX |
|---|---|---|---|---|
| Internal | µs | Yes | Key in app memory | No confirmation prompts |
| Remote (NIP-46) | 100ms–seconds | No (needs bunker reachable) | Key never touches Amethyst | Occasional approval prompts |
| External (NIP-55) | 100–500ms | Yes | Key in separate app | Prompt on every sign by default (configurable) |
Gotchas
- Callbacks may never fire. External signers can be dismissed without result; remote signers can time out. Use
SignerExceptions/ timeout handling at every call site or rely on theAccountlayer's wrapping. nip04Encryptis legacy for NIP-04 DMs. New DM code should use NIP-17 gift-wrap →nip44Encryptpath.- Don't cache signer output beyond the
caches/that quartz already maintains. Stale cache entries lead to duplicate publishes. - Remote signer disconnects need explicit reconnection UX —
RemoteSignerManagerexposes state; hook into it for an account-switching warning. - External signer launch requires an Activity context — it can't happen from a background service. Structure flows so signing is on the main dispatcher through an activity-scoped launcher.
NostrSignerSyncis rare. If you reach for it, you're probably in a test or migration — production code uses the async API.
References
references/nip46-remote-signer.md— the NIP-46 bunker message taxonomy and connection lifecycle.references/nip55-android-signer.md— Android intent-based external signer flow.- Complements:
nostr-expert/references/crypto-and-encryption.md(the crypto under all signers),account-state(which wraps signer calls),android-expert(intent launcher patterns).
Frequently asked questions about Auth Signers
Similar skills
WinMD API Search
Easily find and explore Windows desktop APIs.
WebMCPify
Transform any web app into an agent-ready platform.
Phoenix Tracing
Instrument LLM applications with OpenInference tracing.
Foundry Hosted Agent CopilotKit
Guidance for developing agentic web apps on Azure.
Power Automate Foundation
Connect AI agents to Power Automate seamlessly.
Power Automate Flow Builder
Efficiently build and deploy Power Automate flows programmatically.
