This guide explains how to integrate nsecbunker-java with bottin, a NIP-05 identity registry service.
nsecbunker-java provides interfaces for NIP-05 identity management through the nsecbunker-account module. By default, these use in-memory storage. Bottin provides persistent implementations backed by a database.
┌─────────────────────────────────────────────────────────────────┐
│ Your Application │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ NsecBunkerClient│ │ Nip05Manager │ │
│ │ (signing) │ │ (identity mgmt) │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ ▼ ▼ │
│ nsecbunkerd bottin / in-memory │
│ │
└─────────────────────────────────────────────────────────────────┘
When bottin-spring-boot-starter is on the classpath, nsecbunker-java automatically uses bottin's database-backed implementation through a Service Provider Interface (SPI) pattern. The integration is transparent - your code doesn't need to know which implementation is being used.
┌─────────────────────────────────────────────────────────────────────┐
│ Your Application │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ nsecbunker-spring-boot-starter bottin-spring-boot-starter │
│ ┌───────────────────────────┐ ┌───────────────────────────┐ │
│ │ NsecBunkerAutoConfiguration│ │ BottinAutoConfiguration │ │
│ │ │ │ │ │
│ │ nip05Manager() method: │ │ Creates: │ │
│ │ - Collects all providers │◄───────│ - PersistentNip05Manager │ │
│ │ - Sorts by priority │injects │ - BottinNip05Provider(100)│ │
│ │ - Selects highest (bottin)│ │ │ │
│ └───────────────────────────┘ └───────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ Nip05Manager interface PostgreSQL / H2 │
│ │
└─────────────────────────────────────────────────────────────────────┘
The NsecBunkerAutoConfiguration automatically selects the best available provider:
// From NsecBunkerAutoConfiguration.java
Optional<Nip05ManagerProvider> best = providers.stream()
.filter(Nip05ManagerProvider::isAvailable)
.max(Comparator.comparingInt(Nip05ManagerProvider::priority));When bottin is active, all Nip05Manager operations are database-backed:
| Operation | Method | Description |
|---|---|---|
| Check if taken | verifyNip05(String nip05) |
Returns true if NIP-05 exists and is enabled |
| Store NIP-05 | setupNip05(String username, String domain) |
Creates record with auto-generated identity |
| Lookup pubkey | findByNip05(String nip05) |
Returns the record with pubkey if found |
| Reverse lookup | findByPubkey(String pubkey) |
Find NIP-05 by public key |
| List by domain | findByDomain(String domain) |
List all NIP-05s for a domain |
| Delete | deleteNip05(String nip05) |
Remove a NIP-05 record |
| Update relays | updateRelays(String nip05, List<String> relays) |
Update relay list |
@Service
@RequiredArgsConstructor
public class IdentityService {
// Bottin's PersistentNip05Manager is injected automatically
private final Nip05Manager nip05Manager;
public CompletableFuture<Boolean> isNip05Taken(String nip05) {
// Checks database via bottin
return nip05Manager.verifyNip05(nip05);
}
public CompletableFuture<Nip05Record> registerNip05(String username, String domain) {
// Stores in database via bottin, generates identity via nostr-java
return nip05Manager.setupNip05(username, domain);
}
public CompletableFuture<Optional<String>> lookupPubkey(String nip05) {
// Queries database via bottin
return nip05Manager.findByNip05(nip05)
.thenApply(opt -> opt.map(Nip05Record::getPubkey));
}
}No additional dependencies required. Uses DefaultNip05Manager and DefaultAccountManager.
# application.yml
nsecbunker:
nip05:
enabled: true
provider: in-memory # or "auto" (default)Bottin runs as a separate service. Your application calls its REST API.
@Service
public class Nip05RestClient {
private final RestTemplate restTemplate;
private final String bottinUrl;
public Nip05RecordResponse createNip05(String username, String domain, String pubkey) {
var request = Map.of(
"username", username,
"domain", domain,
"pubkey", pubkey
);
return restTemplate.postForObject(
bottinUrl + "/api/v1/records",
request,
Nip05RecordResponse.class
);
}
}Add bottin as a dependency and it automatically provides persistent implementations.
<dependency>
<groupId>xyz.tcheeric</groupId>
<artifactId>bottin-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency># application.yml
nsecbunker:
nip05:
enabled: true
provider: auto # Will auto-select bottin (priority 100)
spring:
datasource:
url: jdbc:postgresql://localhost:5432/myappnsecbunker-java uses an SPI pattern for pluggable NIP-05 implementations.
public interface Nip05ManagerProvider {
Nip05Manager create();
int priority(); // Higher = more preferred
String name(); // e.g., "in-memory", "bottin"
boolean isAvailable(); // Check if provider can be used
}public interface AccountManagerProvider {
AccountManager create();
int priority();
String name();
boolean isAvailable();
}| Priority | Provider Type |
|---|---|
| 0 | In-memory (default) |
| 50 | Custom implementations |
| 100 | Persistent (bottin) |
@Component
public class RedisNip05ManagerProvider implements Nip05ManagerProvider {
private final RedisTemplate<String, Nip05Record> redis;
public RedisNip05ManagerProvider(RedisTemplate<String, Nip05Record> redis) {
this.redis = redis;
}
@Override
public Nip05Manager create() {
return new RedisNip05Manager(redis);
}
@Override
public int priority() {
return 75; // Between in-memory and persistent
}
@Override
public String name() {
return "redis-cache";
}
@Override
public boolean isAvailable() {
try {
redis.getConnectionFactory().getConnection().ping();
return true;
} catch (Exception e) {
return false;
}
}
}nsecbunker:
nip05:
# Enable/disable NIP-05 functionality
enabled: true
# Provider selection: "auto", "in-memory", "bottin", or custom name
provider: auto
# Default relays for new NIP-05 records
default-relays:
- wss://relay.example.com
# Auto-register NIP-05 when creating keys
auto-register: false
# Default domain for auto-registration
default-domain: example.com@Service
@RequiredArgsConstructor
public class UserRegistrationService {
private final NsecBunkerAdminClient adminClient;
private final Nip05Manager nip05Manager;
public CompletableFuture<UserAccount> registerUser(String username, String domain) {
// 1. Create key in nsecbunkerd
return adminClient.createKey(username)
.thenCompose(bunkerKey -> {
// 2. Create NIP-05 record
return nip05Manager.setupNip05(username, domain)
.thenApply(nip05 -> new UserAccount(bunkerKey, nip05));
});
}
}@Service
@RequiredArgsConstructor
public class IdentityService {
private final Nip05Manager nip05Manager;
public CompletableFuture<Optional<String>> lookupPubkey(String nip05) {
return nip05Manager.findByNip05(nip05)
.thenApply(opt -> opt.map(Nip05Record::getPubkey));
}
public CompletableFuture<Boolean> isRegistered(String nip05) {
return nip05Manager.verifyNip05(nip05);
}
}If you need to serve .well-known/nostr.json:
@RestController
public class WellKnownController {
private final Nip05Manager nip05Manager;
@GetMapping("/.well-known/nostr.json")
public ResponseEntity<String> nostrJson(@RequestParam(required = false) String name) {
// If using bottin, it provides this endpoint automatically
// Otherwise, implement using nip05Manager.findByDomain()
}
}Check the application logs at startup for these messages:
INFO bottin_autoconfiguration_nip05_manager_created
INFO bottin_autoconfiguration_nip05_provider_created
INFO nip05_manager_created provider=bottin-persistent priority=100
If you see provider=in-memory instead, bottin is not being used.
WARN nip05_manager_provider_not_found requested=bottin using=default
Ensure bottin-spring-boot-starter is on the classpath and configured correctly.
If you see circular dependency errors, ensure your custom provider doesn't inject Nip05Manager directly. Use @Lazy or restructure dependencies.
With bottin, ensure the database is accessible:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/bottin
username: ${BOTTIN_DB_USER}
password: ${BOTTIN_DB_PASS}When using setupNip05(), the domain must already be registered and verified in bottin. Otherwise you'll get:
DomainNotFoundException- domain not registeredDomainNotVerifiedException- domain registered but not verified
Register and verify domains via bottin's admin API or dashboard before creating NIP-05 records.