Nostr プロトコルで SNS を作ってビットコインをもらった話
概要
OSS 開発でお金(ビットコイン)をもらうという貴重な経験をしたので記録を残しておこうと思います。
Nostr との出会い
こちらの記事で Nostr のことを知ってシンプルなアーキテクチャに興味を持ちました。
目ざとい方は Twitter が一瞬禁止していた SNS のリストに含まれているのを見て始めていたようです。
簡単に説明をするとクライアントサーバーモデルでサーバーは中継だけを行いクライアントがビジネスロジックを持ちます。
サーバーは WebSocket で実装され、リレーと呼びます。リレーはデータベースへの読み書きとクライアントへのブロードキャスト以外は行いません。
そのためクライアントとリレーを別々に開発/運用することができ、誰かが運用してくれているリレーを使ってクライアント≒サービスの開発を行えます。
ちょうど仕事でサーバーレスな WebSocket サーバーを作っていたこともあってリレーを作ってみたいと思い始めることにしました。
Nostr クライアント開発
リレー開発をしたかったのではないかと疑問に思った方もいると思いますが、当時はまだまだ黎明期で(あくまで自分の基準で)満足に使える SNS クライアントがありませんでした。
また、大昔に Mevy という Twitter クライアントを作っていて実現できていなかったこともたくさんあったのでいい機会かと思い SNS クライアントから作ることにしました。
技術選定
自分はサーバーエンジニアなのでフロントエンドのことはさっぱり分かりません。
なるべくシンプルで学習コストの低いフレームワークがいいと思い Svelte を選択しました。
Web にしたのは昨今の iOS / Android アプリではストアで本名を公開しないといけなかったりするのとクロスプラットフォーム対応をするなら比較的低コストな気がしたからです。(UX を良くしようとするとそれはそれで大変だったりはしますが…)
Nostr のライブラリは nostr-tools と rx-nostr を使っています。
WebSocket と Rx は相性がいいのでリレーとのやり取りに最適です。
最初は Vercel の無料プランでホストしていたのですが無料枠を超えてしまったのと Cloudflare を使いたかったのもあって Cloudflare Pages に移行しました。
サーバーを持つ必要がないので GitHub Pages でホストすることもできますが、ページ毎にパーマリンクや OGP を設定したかったので SSR できるところを選んでいます。
nostter
フロントエンドに四苦八苦しながら作っているクライアントがこちらです。
大変ありがたいことに UI やロゴを作ってくださる方がいたりしてなんとか形になっています。
SNS に求められる機能はとても多いので現在も随時機能を追加しています。
OpenSats というビットコインファンド
ビットコインの発展に役立つ OSS 貢献者に持続可能な資金を提供するファンドです。
Nostr も対象となっています。
受け取りはビットコインになります。
クライアントがある程度形になったところで申請したら承諾いただけたので(仕事が空いたタイミングだったのもあり)今年は Nostr 開発で生活費を賄うことができました。
期間は契約時に決まる(自分の場合は 1 年でした)ので契約更新は再申請する必要があります。
自分以外にも日本人でもらっている方は数名いたりします。(期間はまちまちです)
そのうちの 1 人の mono さんの記事。
Nostr リレー開発
今月からようやく本格的にリレー開発に着手しました。
Nostr に関する知見も十分に貯まっているのでコードを書くだけです。
技術選定
AWS Lambda だと課金の相性が悪そうだったのとコールドスタートがあるので Cloudflare Workers + Durable Objects (要 Workers Paid プラン)を選択しました。
Deno Deploy でも WebSocket を扱えるようなのでそのうち試してみたいところです。
サーバーレス WebSocket に関する知見はこちらにまとめてあります。
データベースは最近ベータ提供が始まった Durable Objects 付属の SQL Storage にするか D1 にするかで迷っています。
価格や制限は同じようなので中身もほぼ同じなのかもしれません。
申請すれば増やせるようですがデータベースの容量が 10GB までなのは少し気になるところ…
フレームワークは Hono を使っています。
こちらもシンプルで学習コストが低いのでサーバー開発にはおすすめです。
やや特殊な環境なので困っていること
- Nostr ではリレーそのものである WebSocket (wss://) と リレー情報を提供する JSON (https://) を同じ URI に置かないといけないので cors ミドルウェアをそのまま使えない。
- ミドルウェアを自分で定義して分岐は書けるけど条件式が二重管理になる。
- WebSocket on Durable Objects のテストの書き方がよく分からない。
diff --git a/src/index.ts b/src/index.ts
index 24eb2e5..59c44b8 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,6 +1,7 @@
import { Hono } from "hono";
import { Relay } from "./relay";
import { nip11 } from "./config";
+import { cors } from "hono/cors";
type Bindings = {
RELAY: DurableObjectNamespace<Relay>;
@@ -8,13 +9,25 @@ type Bindings = {
const app = new Hono<{ Bindings: Bindings }>();
+app.use(async (c, next) => {
+ if (c.req.header("Accept") === "application/nostr+json") {
+ const handler = cors({
+ origin: "*",
+ allowMethods: ["GET"],
+ });
+ await handler(c, next);
+ } else {
+ await next();
+ }
+});
+
app.get("/", (c) => {
if (c.req.header("Upgrade") === "websocket") {
const id = c.env.RELAY.idFromName("relay");
const stub = c.env.RELAY.get(id);
return stub.fetch(c.req.raw);
} else if (c.req.header("Accept") === "application/nostr+json") {
- c.header("Access-Control-Allow-Origin", "*");
return c.json(nip11);
} else {
return c.text("Hello Hono!");
@@ -22,7 +35,7 @@ app.get("/", (c) => {
});
app.options("/", (c) => {
- c.header("Access-Control-Allow-Methods", "GET");
return new Response(null, {
status: 204,
});
Nostr ライブラリは今のところ nostr-tools だけで事足りそうです。
Snowflare
開発中のリレーがこちらです。まだ開発を始めたばかりなのでホストはしていません。
最後に
Nostr プロトコルは SNS に限ったものではないのでデータベースを自分で用意しなくても様々なサービスを開発することができます。興味がある方はぜひ遊んでみてください。
OpenSats はまだまだビットコインや Nostr に関連する OSS 開発者を探しているようですし Zap という投げ銭の仕組みもあるので何か作るとビットコインがもらえるかもしれません。
Discussion