nostr account actor

This commit is contained in:
randogoth 2026-06-08 12:25:26 +03:00
parent 305cff62bd
commit 0cdf3d7dbf
6 changed files with 1183 additions and 0 deletions

View file

@ -265,3 +265,74 @@ final result = await actor.handle(
final url = (result as FileUploaded).cdnUrl;
// https://cdn.otherwhere.app/<app>/<pubkey>/<uuid>.jpg
```
---
## NostrAccountActor
Manages a Nostr user identity: keypair lifecycle, BIP-39 mnemonic support (NIP-06
derivation), and kind-0 profile fetch/publish via injected helper actors.
```dart
final actor = NostrAccountActor(
fetcher: NostrFetchActor(relayUrls: ['wss://relay.example.com']),
publisher: NostrPublishActor(relayUrls: ['wss://relay.example.com']),
);
```
| Parameter | Type | Default | Description |
|---|---|---|---|
| `fetcher` | `NostrFetchActor?` | null | Required for `FetchProfile` |
| `publisher` | `NostrPublishActor?` | null | Required for `UpdateProfile` |
Both helpers are optional at construction; passing neither is valid when only using
key management messages.
| Message | Result | Description |
|---|---|---|
| `CreateAccount()` | `AccountCreated(publicKeyHex, mnemonic)` | Generates a new keypair from 128-bit entropy via BIP-39 + NIP-06 derivation |
| `ImportAccount(privateKeyHex)` | `AccountImported(publicKeyHex)` | Loads an existing raw hex private key. `ExportMnemonic` returns null afterwards. |
| `ImportMnemonic(mnemonic)` | `AccountImported(publicKeyHex)` | Derives keypair from a 12-word BIP-39 mnemonic via NIP-06 path `m/44'/1237'/0'/0/0` |
| `GetAccountPublicKey()` | `AccountPublicKeyResult(publicKeyHex)` | Returns the loaded account's public key |
| `ExportPrivateKey()` | `PrivateKeyExported(privateKeyHex)` | Returns the raw private key |
| `ExportMnemonic()` | `MnemonicExported(mnemonic?)` | Returns the BIP-39 mnemonic, or null if the account was imported via raw private key |
| `FetchProfile()` | `ProfileFetched(profile?)` | Fetches kind-0 from relay; caches result for `GetPublicData` |
| `UpdateProfile(profile)` | `ProfileUpdated()` | Publishes a kind-0 event; caches profile for `GetPublicData` |
| `GetPublicData()` | `PublicData(data)` | Returns a `Map<String, dynamic>` with `pubkey` plus any cached profile fields |
**NostrProfile** fields (all optional): `name`, `displayName`, `about`, `picture`,
`banner`, `website`, `nip05`, `lud16`.
All messages except Create/Import throw `StateError` if called before the account is
loaded. `FetchProfile` throws if no `fetcher` was provided; `UpdateProfile` throws if
no `publisher` was provided.
```dart
final fetcher = NostrFetchActor(relayUrls: ['wss://relay.otherwhere.app/']);
final publisher = NostrPublishActor(relayUrls: ['wss://relay.otherwhere.app/']);
final actor = NostrAccountActor(fetcher: fetcher, publisher: publisher);
// New user
final created = await actor.handle(const CreateAccount()) as AccountCreated;
print(created.mnemonic); // 12-word backup phrase
print(created.publicKeyHex); // hex pubkey
// Returning user — restore from mnemonic
await actor.handle(ImportMnemonic(savedMnemonic));
// Or restore from raw key
await actor.handle(ImportAccount(savedPrivkeyHex));
// Update profile
await actor.handle(
UpdateProfile(const NostrProfile(name: 'alice', nip05: 'alice@example.com')),
);
// Fetch latest profile from relay
final fetched = await actor.handle(const FetchProfile()) as ProfileFetched;
print(fetched.profile?.name);
// Public-facing data (pubkey + cached profile)
final pub = await actor.handle(const GetPublicData()) as PublicData;
print(pub.data); // {'pubkey': '...', 'name': 'alice', 'nip05': 'alice@example.com'}
```