An Android library that provides implementations of Nostr Improvement Proposals (NIPs), specifically designed to be used in plugins for cross-platform toolkits like Capacitor or Flutter, but can also be easily integrated into existing Android projects. Currently, the library implements NIP-55.
The Nostr Android Library enables Android applications to interact with the Nostr decentralized protocol by providing native implementations of Nostr Improvement Proposals (NIPs). The library is designed to be modular and easily integrated into cross-platform toolkits such as Capacitor or Flutter, as well as into existing Android projects.
Currently, the library implements NIP-55, which defines the protocol for application-level signing of Nostr events and data exchange with Android signer apps.
- NIP-55 Implementation: Provides methods for signing Nostr events as per NIP-55.
- Easy Integration: Designed to be easily integrated into Android projects or cross-platform plugins.
- Modular Design: Built to accommodate future implementations of additional NIPs.
Add the JitPack repository (choose one style depending on your Gradle layout):
Option A — settings.gradle (newer projects)
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url "https://jitpack.io" }
}
}Note: JitPack will honor the coordinates defined by the project's POM (groupId, artifactId, version).
Option B — root build.gradle (older projects)
allprojects {
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}Then add the dependency to your Android app/module. This repo publishes the Gradle submodule :android, but the published artifactId is set to nostr-android via Maven Publish:
dependencies {
// Use a git tag or commit SHA. Example for v2.0.0:
implementation 'biz.nostr:nostr-android:2.0.0'
}dependencies {
implementation 'biz.nostr:nostr-android:1.0.0'
}Signer and IntentBuilder expose static methods.
import biz.nostr.android.nip55.Signer;
import biz.nostr.android.nip55.IntentBuilder;These calls should be done off the main thread.
String packageName = /* previously selected signer package, e.g., from interactive get_public_key */;
String npub = Signer.getPublicKey(context, packageName); // returns null if not remembered or rejected
String[] signed = Signer.signEvent(context, packageName, eventJson, currentUserNpub);
// signed[0] = result (signature), signed[1] = event (signed event JSON)
String enc = Signer.nip04Encrypt(context, packageName, plainText, recipientHexPubKey, currentUserNpub);
String dec = Signer.nip04Decrypt(context, packageName, encryptedText, senderHexPubKey, currentUserNpub);
String enc44 = Signer.nip44Encrypt(context, packageName, plainText, recipientHexPubKey, currentUserNpub);
String dec44 = Signer.nip44Decrypt(context, packageName, encryptedText, senderHexPubKey, currentUserNpub);
String zapEventJson = Signer.decryptZapEvent(context, packageName, eventJson, currentUserNpub);
String relaysJson = Signer.getRelays(context, packageName, currentUserNpub);// First, discover if any signer is installed
boolean hasSigner = Signer.isExternalSignerInstalled(context);
// Request public key interactively (with optional permissions JSON)
String permissionsJson = "[ {\"type\":\"sign_event\",\"kind\":22242}, {\"type\":\"nip44_decrypt\"} ]";
Intent getPk = IntentBuilder.getPublicKeyIntent("com.example.signer", permissionsJson);
// Optionally add singleTop flags when batching
IntentBuilder.withSingleTopFlags(getPk);
activityResultLauncher.launch(getPk);
// Sign event interactively
Intent sign = IntentBuilder.signEventIntent("com.example.signer", eventJson, eventId, currentUserNpub);
IntentBuilder.withSingleTopFlags(sign);
activityResultLauncher.launch(sign);On result, per NIP-55, read extras: result, id, and optionally event for sign_event.
Key static methods (see source android/src/main/java/biz/nostr/android/nip55/Signer.java):
List<ResolveInfo> isExternalSignerInstalled(Context, String)andboolean isExternalSignerInstalled(Context)boolean isSignerPackageAvailable(Context, String)String getPublicKey(Context, String packageName)— returns result (npub) or nullString[] signEvent(Context, String packageName, String eventJson, String currentUserNpub)— returns { result, event } or nullString nip04Encrypt/Decrypt(...),String nip44Encrypt/Decrypt(...),String decryptZapEvent(...),String getRelays(...)— return result or null
Contributions are welcome! If you'd like to contribute to this project, please follow these steps:
-
Fork the Repository: Click the 'Fork' button at the top right of the repository page.
-
Clone Your Fork:
git clone https://github.com/yourusername/NostrAndroid.git cd nostr-android-library -
Create a New Branch:
git checkout -b feature/your-feature-name
-
Make Your Changes: Implement your feature or bug fix.
-
Commit Your Changes:
git commit -am 'Add some feature' -
Push to the Branch:
git push origin feature/your-feature-name
-
Open a Pull Request: Go to the repository on GitHub and click 'New pull request'.
This project is licensed under the MIT License. See the LICENSE file for details.
Note: For any issues or questions, please open an issue on the GitHub repository.
- Android SDK: minSdk 21+, compileSdk 34
- Java Version: Java 8
- Android: Supported
- Cross-Platform Toolkits: Compatible with Capacitor, Flutter, and others
Permissions are represented as a JSON array of objects: [{ "type": string, "kind"?: number }].
Examples:
- TypeScript / Capacitor / React Native
type Permission = { type: string; kind?: number };
const permissions: Permission[] = [
{ type: 'sign_event', kind: 22242 },
{ type: 'nip44_decrypt' },
];
const permissionsJson = JSON.stringify(permissions);- Flutter / Dart
class Permission {
final String type; final int? kind; const Permission(this.type, {this.kind});
Map<String, dynamic> toJson() => { 'type': type, if (kind != null) 'kind': kind };
}
final permissionsJson = jsonEncode([Permission('sign_event', kind: 22242).toJson()]);Pass permissionsJson to IntentBuilder.getPublicKeyIntent(packageName, permissionsJson).
If you need to build the library locally:
-
Clone the Repository:
git clone https://github.com/chebizarro/NostrAndroid.git cd nostr-android-library -
Build the Library:
./gradlew build
-
Publish to Local Maven Repository (Optional):
./gradlew publishToMavenLocal
To use the library in cross-platform plugins:
- Flutter: Add the dependency to the Android module of your plugin and forward method calls/permissions JSON.
- Capacitor / React Native: Serialize permissions and other parameters at the JS/TS boundary and pass strings to Android.
- No signer found: ensure you added the
queriessection in your app manifest per NIP-55 and checkSigner.isExternalSignerInstalled(context). - Wrong results from provider: ensure the signer packageName is validated and constant between calls; store the package from the initial interactive
get_public_keyresponse. - Intent payload corrupted: use
IntentBuildermethods that URI-encode payloads. - Publishing/consumption issues on JitPack: pin a git tag or SHA. For Maven Central, ensure you’re using the documented coordinates.
Some older signer implementations may return a legacy column name signature instead of the NIP-55-compliant result.
This library prefers result and will transparently fall back to signature when result is absent. You can enable a debug log when the legacy fallback is taken:
// Optional: enable to log when legacy 'signature' column is used
biz.nostr.android.nip55.Signer.setLegacyFallbackLogging(true);Thank you for using the Nostr Android Library!
This library includes a Java-only implementation for parsing and building nostr: URIs (NIP-21). It is dependency-free and framework-agnostic.
import biz.nostr.android.nip21.NostrUri;
import biz.nostr.android.nip21.NostrUriParser;
NostrUri u = NostrUriParser.parse("nostr:npub1sn0wdenkukak0d9dfczzeacvhkrgz92ak56egt7vdgzn8pv2wfqqhrjdv9");
// u.getKind() == NostrUri.Kind.NPUB
// u.getBech32() == "npub1sn0wdenkukak0d9dfczzeacvhkrgz92ak56egt7vdgzn8pv2wfqqhrjdv9"
// u.getQuery().isEmpty() == trueWith a query string:
NostrUri u = NostrUriParser.parse("nostr:note1fntxtkcy9...?...&relay=wss%3A%2F%2Frelay.example");
String relay = u.getQuery().get("relay"); // "wss://relay.example"Safety: nsec is rejected by design.
import biz.nostr.android.nip21.NostrUriBuilder;
String s1 = NostrUriBuilder.build("npub1abcd..."); // "nostr:npub1abcd..."
Map<String,String> q = new java.util.HashMap<>();
q.put("relay", "wss://ex.com");
String s2 = NostrUriBuilder.build("nevent1xyz...", q); // "nostr:nevent1xyz...?relay=wss%3A%2F%2Fex.com"import biz.nostr.android.nip21.NostrDeepLinkHelper;
boolean isNostr = NostrDeepLinkHelper.isNostrDeepLink(intent);
NostrUri parsed = NostrDeepLinkHelper.parseFromIntent(intent);
Intent view = NostrDeepLinkHelper.buildViewIntent("note1...", java.util.Collections.singletonMap("relay", "wss://relay.example"));Add a queries block to discover nostr handlers:
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="nostr" />
</intent>
<!-- existing nostrsigner queries may already be present -->
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="nostrsigner" />
</intent>
</queries>In your activity to receive deep links:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="nostr" />
<!-- optionally host/path filters if you choose to support http(s) indirection -->
</intent-filter>Notes:
nsecis blocked and will throw an exception when parsed or built.- This module performs minimal bech32 shape validation and classifies by prefix; no TLV decoding is performed here.