revolution/laravel-nostr는 Nostr 프로토콜을 Laravel에서 이용하기 위한 패키지입니다. Key 생성·변환, 이벤트의 조회·발행, pool (복수 릴레이) 대응, NIP-05 프로필, NIP-17 Private Direct Messages, 그리고 Laravel Notifications와의 통합을 제공합니다.
Nostr 사양은 아직 진화 중이므로 이 패키지도 지속적으로 개발되고 있습니다. 알림 기능은 이미 실용적으로 사용할 수 있습니다.
드라이버
이 패키지에는 2개의 드라이버가 있습니다.
| 드라이버 | 설명 |
|---|
native | PHP만으로 동작하는 구현. nostr-php 사용. 현재는 이것으로 충분합니다. |
node | 외부 WebAPI (Node.js)에 의존하는 구현. |
native 드라이버의 독특한 점은 WebSocketHttpMixin 구현입니다. Laravel의 HTTP 클라이언트로 WebSocket에 연결하고, 데이터를 송수신하면 곧바로 연결을 끊습니다. WebSocket 서버를 계속 켜둘 필요가 없어 Laravel 사용자라면 누구나 사용할 수 있는 설계입니다.
native 드라이버는 NIP-04에 대응하지 않습니다.
기본 드라이버 설정
config/nostr.php 또는 .env에서 설정합니다.
// config/nostr.php
'driver' => env('NOSTR_DRIVER', 'node'),
드라이버를 지정하지 않으면 기본 드라이버가 사용됩니다.
use Revolution\Nostr\Facades\Nostr;
Nostr::event()->list();
드라이버를 명시적으로 지정할 수도 있습니다.
use Revolution\Nostr\Facades\Nostr;
Nostr::driver('node')->event()->list();
Nostr::node()->event()->list();
Nostr::driver('native')->event()->list();
Nostr::native()->event()->list();
패키지 설치
composer require revolution/laravel-nostr
설정 파일 게시
php artisan vendor:publish --tag=nostr-config
Key 관리
Key 생성
use Revolution\Nostr\Facades\Nostr;
use Illuminate\Http\Client\Response;
/** @var Response $response */
$response = Nostr::key()->generate();
$keys = $response->json();
// [
// 'sk' => 'sk...',
// 'nsec' => 'nsec...',
// 'pk' => 'pk...',
// 'npub' => 'npub...',
// ]
Key 변환
nsec에서 변환합니다.
use Revolution\Nostr\Facades\Nostr;
$response = Nostr::key()->fromNsec(nsec: 'nsec');
$keys = $response->json();
// ['sk' => '...', 'nsec' => '...', 'pk' => '...', 'npub' => '...']
Secret key에서 변환합니다.
$response = Nostr::key()->fromSecretKey(sk: 'sk');
npub에서 변환합니다 (공개키만).
$response = Nostr::key()->fromNpub(npub: 'npub');
$keys = $response->json();
// ['pk' => '...', 'npub' => '...']
Public key에서 변환합니다.
$response = Nostr::key()->fromPublicKey(pk: 'pk');
이벤트 조회
여러 이벤트 조회
use Illuminate\Http\Client\Response;
use Revolution\Nostr\Facades\Nostr;
use Revolution\Nostr\Filter;
use Revolution\Nostr\Kind;
$filter = Filter::make(
authors: ['my pk'],
kinds: [Kind::Text],
limit: 10,
);
/** @var Response $response */
$response = Nostr::event()->list(filter: $filter);
$events = $response->json('events');
// [
// ['id' => '...1', 'kind' => 1, 'content' => '...'],
// ['id' => '...2', 'kind' => 1, 'content' => '...'],
// ]
1건의 이벤트 조회
use Revolution\Nostr\Facades\Nostr;
use Revolution\Nostr\Filter;
use Revolution\Nostr\Kind;
$filter = Filter::make(
authors: ['my pk'],
kinds: [Kind::Metadata],
);
$response = Nostr::event()->get(filter: $filter);
$event = $response->json('event');
// ['id' => '...', 'kind' => 0, 'content' => '{name: ""}']
이벤트 발행
단일 릴레이에 발행
use Revolution\Nostr\Facades\Nostr;
use Revolution\Nostr\Event;
use Revolution\Nostr\Kind;
$event = Event::make(
kind: Kind::Text,
content: 'hello',
created_at: now()->timestamp,
tags: [],
);
$sk = 'my sk';
$response = Nostr::event()->publish(event: $event, sk: $sk);
if ($response->successful()) {
$event = $response->json('event');
}
여러 릴레이에 발행 (pool)
use Revolution\Nostr\Facades\Nostr;
use Revolution\Nostr\Event;
use Revolution\Nostr\Kind;
$event = Event::make(
kind: Kind::Text,
content: 'test',
created_at: now()->timestamp,
tags: [],
);
$responses = Nostr::pool()->publish(event: $event, sk: 'my sk');
// $responses는 array<string, Response>
// ['wss://relay1' => $response, 'wss://relay2' => $response]
foreach ($responses as $relay => $response) {
if ($response->failed()) {
dump($relay . ' : ' . $response->body());
}
}
릴레이 서버 설정
사용되는 릴레이 서버
Nostr::event()만 사용할 경우 config/nostr.php의 첫 번째 릴레이가 사용됩니다. Nostr::pool()을 사용하는 경우 설정 내 모든 릴레이가 대상이 됩니다.
런타임에 릴레이 변경
use Revolution\Nostr\Facades\Nostr;
$response = Nostr::event()->withRelay('wss://')->...;
use Revolution\Nostr\Facades\Nostr;
$response = Nostr::pool()->withRelays(['wss://', 'wss://'])->...;
NIP-05 프로필
use Revolution\Nostr\Facades\Nostr;
$profile = Nostr::nip05()->profile('user@localhost');
// [
// 'user' => 'user@localhost',
// 'pubkey' => 'pk',
// 'relays' => [],
// ]
NIP-17 Private Direct Messages
NIP-17은 native 드라이버에서만 지원됩니다.
프라이빗 메시지 전송
use Revolution\Nostr\Facades\Nostr;
$response = Nostr::driver('native')
->nip17()
->sendDirectMessage(
sk: 'sender-secret-key',
pk: 'receiver-public-key',
message: 'Hello, this is a private message!'
);
프라이빗 메시지 복호화
use Revolution\Nostr\Facades\Nostr;
$response = Nostr::driver('native')
->nip17()
->decryptDirectMessage(
giftWrap: $receivedGiftWrap,
sk: 'receiver-secret-key'
);
$decryptedMessage = $response->json();
Laravel Notifications
NostrChannel을 사용하면 Laravel Notifications에서 Nostr로 메시지를 전송할 수 있습니다.
Notification 클래스
use Illuminate\Notifications\Notification;
use Revolution\Nostr\Notifications\NostrChannel;
use Revolution\Nostr\Notifications\NostrMessage;
use Revolution\Nostr\Tags\HashTag;
class TestNotification extends Notification
{
public function via(object $notifiable): array
{
return [
'mail',
NostrChannel::class,
];
}
public function toNostr(object $notifiable): NostrMessage
{
return new NostrMessage(
// content 내의 #laravel은 표시용, tags의 HashTag는 프로토콜 레벨의 분류용
content: 'hello #laravel',
tags: [
HashTag::make(t: 'laravel'),
],
);
}
}
온디맨드 알림
use Illuminate\Support\Facades\Notification;
use Revolution\Nostr\Notifications\NostrRoute;
Notification::route('nostr', NostrRoute::to(sk: 'sk'))
->notify(new TestNotification());
User 모델 통합
use Illuminate\Notifications\Notifiable;
use Revolution\Nostr\Notifications\NostrRoute;
class User
{
use Notifiable;
public function routeNotificationForNostr($notification): NostrRoute
{
return NostrRoute::to(sk: $this->sk, relays: ['wss://']);
}
}
$user->notify(new TestNotification());
알림에서 사용하는 릴레이 서버
기본적으로 config/nostr.php의 모든 릴레이가 사용됩니다. NostrRoute에서 릴레이를 지정하면 런타임에 변경할 수 있습니다.
use Revolution\Nostr\Notifications\NostrRoute;
return NostrRoute::to(sk: 'sk', relays: ['wss://', 'wss://']);