Skip to content

Commit

Permalink
feat: add nip-05
Browse files Browse the repository at this point in the history
  • Loading branch information
im-adithya committed Dec 13, 2024
1 parent 83cecef commit 9624792
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 26 deletions.
2 changes: 2 additions & 0 deletions drizzle/0002_crazy_sauron.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE "users" ADD COLUMN "nostr_pubkey" text;--> statement-breakpoint
ALTER TABLE "users" ADD CONSTRAINT "users_nostr_pubkey_unique" UNIQUE("nostr_pubkey");
209 changes: 209 additions & 0 deletions drizzle/meta/0002_snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
{
"id": "0158a5e2-5076-4051-8e8d-2d382552344d",
"prevId": "52607bd8-7a1a-4a34-ad27-91b78733e85c",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.invoices": {
"name": "invoices",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"amount": {
"name": "amount",
"type": "bigint",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"payment_request": {
"name": "payment_request",
"type": "text",
"primaryKey": false,
"notNull": true
},
"payment_hash": {
"name": "payment_hash",
"type": "text",
"primaryKey": false,
"notNull": true
},
"preimage": {
"name": "preimage",
"type": "text",
"primaryKey": false,
"notNull": false
},
"metadata": {
"name": "metadata",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"settled_at": {
"name": "settled_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"user_id_idx": {
"name": "user_id_idx",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"user_payment_hash_idx": {
"name": "user_payment_hash_idx",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "payment_hash",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"invoices_user_id_users_id_fk": {
"name": "invoices_user_id_users_id_fk",
"tableFrom": "invoices",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"invoices_payment_request_unique": {
"name": "invoices_payment_request_unique",
"nullsNotDistinct": false,
"columns": [
"payment_request"
]
},
"invoices_payment_hash_unique": {
"name": "invoices_payment_hash_unique",
"nullsNotDistinct": false,
"columns": [
"payment_hash"
]
}
}
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"connection_secret": {
"name": "connection_secret",
"type": "text",
"primaryKey": false,
"notNull": true
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"nostr_pubkey": {
"name": "nostr_pubkey",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_username_unique": {
"name": "users_username_unique",
"nullsNotDistinct": false,
"columns": [
"username"
]
},
"users_nostr_pubkey_unique": {
"name": "users_nostr_pubkey_unique",
"nullsNotDistinct": false,
"columns": [
"nostr_pubkey"
]
}
}
}
},
"enums": {},
"schemas": {},
"sequences": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
7 changes: 7 additions & 0 deletions drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@
"when": 1733813329314,
"tag": "0001_white_prism",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1734078845730,
"tag": "0002_crazy_sauron",
"breakpoints": true
}
]
}
7 changes: 5 additions & 2 deletions src/db/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export class DB {

async createUser(
connectionSecret: string,
username?: string
username?: string,
nostrPubkey?: string
) {
const parsed = nwc.NWCClient.parseWalletConnectUrl(connectionSecret);
if (!parsed.secret) {
Expand All @@ -43,7 +44,8 @@ export class DB {
const [newUser] = await this._db.insert(users).values({
encryptedConnectionSecret,
username,
}).returning({ id: users.id, username: users.username });
nostrPubkey
}).returning({ id: users.id, username: users.username, nostrPubkey: users.nostrPubkey });

return newUser;
}
Expand All @@ -62,6 +64,7 @@ export class DB {
const connectionSecret = await decrypt(result.encryptedConnectionSecret);
return {
id: result.id,
nostrPubkey: result.nostrPubkey,
connectionSecret
};
}
Expand Down
1 change: 1 addition & 0 deletions src/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const users = pgTable("users", {
id: serial("id").primaryKey(),
encryptedConnectionSecret: text("connection_secret").notNull(),
username: text("username").unique().notNull(),
nostrPubkey: text("nostr_pubkey").unique(),
createdAt: timestamp("created_at").notNull().defaultNow(),
});

Expand Down
26 changes: 24 additions & 2 deletions src/lnurlp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ function getLnurlMetadata(username: string): string {
])
}

export function createLnurlWellKnownApp(db: DB) {
export function createWellKnownApp(db: DB) {
const hono = new Hono();

hono.get("/:username", async (c) => {
hono.get("/lnurlp/:username", async (c) => {
try {
const username = c.req.param("username");

Expand All @@ -41,6 +41,28 @@ export function createLnurlWellKnownApp(db: DB) {
}
});

hono.get("/nostr.json", async (c) => {
try {
const username = c.req.query("name");

logger.debug("NIP05 request", { username });

if (!username) {
throw new Error("No username provided");
}

const user = await db.findUser(username);

return c.json({
names: {
[username]: user.nostrPubkey
}
});
} catch (error) {
return c.json({ status: "ERROR", reason: "" + error });
}
});

return hono;
}

Expand Down
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { secureHeaders } from "hono/secure-headers";
//import { sentry } from "npm:@hono/sentry";
import { PORT } from "./constants.ts";
import { DB, runMigration } from "./db/db.ts";
import { createLnurlApp, createLnurlWellKnownApp } from "./lnurlp.ts";
import { createLnurlApp, createWellKnownApp } from "./lnurlp.ts";
import { LOG_LEVEL, logger, loggerMiddleware } from "./logger.ts";
import { NWCPool } from "./nwc/nwcPool.ts";
import { createUsersApp } from "./users.ts";
Expand All @@ -26,7 +26,7 @@ hono.use(secureHeaders());
hono.use("*", sentry({ dsn: SENTRY_DSN }));
}*/

hono.route("/.well-known/lnurlp", createLnurlWellKnownApp(db));
hono.route("/.well-known", createWellKnownApp(db));
hono.route("/lnurlp", createLnurlApp(db));
hono.route("/users", createUsersApp(db, nwcPool));

Expand Down
54 changes: 34 additions & 20 deletions src/users.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Hono } from "hono";
import postgres from "postgres";
import { DOMAIN } from "./constants.ts";
import { DB } from "./db/db.ts";
import { logger } from "./logger.ts";
Expand All @@ -8,27 +9,40 @@ export function createUsersApp(db: DB, nwcPool: NWCPool) {
const hono = new Hono();

hono.post("/", async (c) => {
logger.debug("create user", {});

const createUserRequest: { connectionSecret: string; username?: string } =
await c.req.json();

if (!createUserRequest.connectionSecret) {
return c.text("no connection secret provided", 400);
try {
logger.debug("create user", {});

const createUserRequest: { connectionSecret: string; username?: string, nostrPubkey?: string } =
await c.req.json();

if (!createUserRequest.connectionSecret) {
return c.text("no connection secret provided", 400);
}

const user = await db.createUser(
createUserRequest.connectionSecret,
createUserRequest.username,
createUserRequest.nostrPubkey
);

const lightningAddress = user.username + "@" + DOMAIN;

nwcPool.subscribeUser(createUserRequest.connectionSecret, user.id);

return c.json({
lightningAddress,
});
} catch (error) {
let reason = "" + error
if (error instanceof postgres.PostgresError) {
if (error.constraint_name === "users_username_unique") {
reason = "Username has already been taken"
} else if (error.constraint_name === "users_nostr_pubkey_unique") {
reason = "Nostr pubkey has already been taken"
}
}
return c.json({ status: "ERROR", reason });
}

const user = await db.createUser(
createUserRequest.connectionSecret,
createUserRequest.username
);

const lightningAddress = user.username + "@" + DOMAIN;

nwcPool.subscribeUser(createUserRequest.connectionSecret, user.id);

return c.json({
lightningAddress,
});
});
return hono;
}

0 comments on commit 9624792

Please sign in to comment.