Skip to content

Commit

Permalink
Merge branch 'master' into task-zapper
Browse files Browse the repository at this point in the history
  • Loading branch information
im-adithya committed Dec 11, 2024
2 parents e9f04c8 + 83cecef commit 69c7209
Show file tree
Hide file tree
Showing 11 changed files with 41 additions and 96 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ A minimal Lightning address server powered by [NWC](https://nwc.dev)

```json
{
"connectionSecret": "nostr+walletconnect://..."
"connectionSecret": "nostr+walletconnect://..."
}
```

Expand All @@ -26,7 +26,6 @@ A minimal Lightning address server powered by [NWC](https://nwc.dev)

- [Install Deno](https://docs.deno.com/runtime/manual/getting_started/installation/)
- Copy `.env.example` to `.env`
- Setup DB: `deno task db:migrate`
- Run in dev mode: `deno task dev`

### Creating a new migration
Expand Down
4 changes: 4 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
CREATE TABLE IF NOT EXISTS "invoices" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" integer NOT NULL,
"amount" integer NOT NULL,
"amount" bigint NOT NULL,
"description" text,
"description_hash" text,
"payment_request" text NOT NULL,
"payment_hash" text NOT NULL,
"preimage" text,
Expand All @@ -21,6 +20,4 @@ EXCEPTION
END $$;
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "user_id_idx" ON "invoices" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "payment_hash_idx" ON "invoices" USING btree ("payment_hash");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "user_payment_hash_idx" ON "invoices" USING btree ("user_id","payment_hash");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "username_idx" ON "users" USING btree ("username");
CREATE INDEX IF NOT EXISTS "user_payment_hash_idx" ON "invoices" USING btree ("user_id","payment_hash");
43 changes: 3 additions & 40 deletions drizzle/meta/0001_snapshot.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"id": "a90a8c91-5f2c-48da-9109-ae0a37820ceb",
"id": "52607bd8-7a1a-4a34-ad27-91b78733e85c",
"prevId": "e292703e-d08b-4f8b-a9eb-3937fe872be7",
"version": "7",
"dialect": "postgresql",
Expand All @@ -22,7 +22,7 @@
},
"amount": {
"name": "amount",
"type": "integer",
"type": "bigint",
"primaryKey": false,
"notNull": true
},
Expand All @@ -32,12 +32,6 @@
"primaryKey": false,
"notNull": false
},
"description_hash": {
"name": "description_hash",
"type": "text",
"primaryKey": false,
"notNull": false
},
"payment_request": {
"name": "payment_request",
"type": "text",
Expand Down Expand Up @@ -92,21 +86,6 @@
"method": "btree",
"with": {}
},
"payment_hash_idx": {
"name": "payment_hash_idx",
"columns": [
{
"expression": "payment_hash",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"user_payment_hash_idx": {
"name": "user_payment_hash_idx",
"columns": [
Expand Down Expand Up @@ -192,23 +171,7 @@
"default": "now()"
}
},
"indexes": {
"username_idx": {
"name": "username_idx",
"columns": [
{
"expression": "username",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
Expand Down
4 changes: 2 additions & 2 deletions drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
{
"idx": 1,
"version": "7",
"when": 1732639452062,
"tag": "0001_fluffy_black_tom",
"when": 1733813329314,
"tag": "0001_white_prism",
"breakpoints": true
}
]
Expand Down
19 changes: 9 additions & 10 deletions src/db/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class DB {
async createUser(
connectionSecret: string,
username?: string
): Promise<{ username: string }> {
) {
const parsed = nwc.NWCClient.parseWalletConnectUrl(connectionSecret);
if (!parsed.secret) {
throw new Error("no secret found in connection secret");
Expand All @@ -40,12 +40,12 @@ export class DB {

const encryptedConnectionSecret = await encrypt(connectionSecret);

await this._db.insert(users).values({
const [newUser] = await this._db.insert(users).values({
encryptedConnectionSecret,
username,
});
}).returning({ id: users.id, username: users.username });

return { username };
return newUser;
}

getAllUsers() {
Expand All @@ -69,31 +69,30 @@ export class DB {
async createInvoice(
userId: number,
transaction: nwc.Nip47Transaction
): Promise<{ identifier: string }> {
) {
await this._db.insert(invoices).values({
userId,
amount: transaction.amount,
description: transaction.description,
descriptionHash: transaction.description_hash,
paymentRequest: transaction.invoice,
paymentHash: transaction.payment_hash,
metadata: transaction.metadata,
});

return { identifier: transaction.payment_hash };
return;
}

async findInvoice(identifier: string) {
async findInvoice(paymentHash: string) {
const result = await this._db.query.invoices.findFirst({
where: eq(invoices.paymentHash, identifier),
where: eq(invoices.paymentHash, paymentHash),
});
if (!result) {
throw new Error("invoice not found");
}
return result;
}

async updateInvoice(
async markInvoiceSettled(
userId: number,
transaction: nwc.Nip47Transaction
): Promise<void> {
Expand Down
10 changes: 2 additions & 8 deletions src/db/schema.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import { index, integer, jsonb, pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";
import { bigint, index, integer, jsonb, pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";

export const users = pgTable("users", {
id: serial("id").primaryKey(),
encryptedConnectionSecret: text("connection_secret").notNull(),
username: text("username").unique().notNull(),
createdAt: timestamp("created_at").notNull().defaultNow(),
}, (table) => {
return {
usernameIdx: index("username_idx").on(table.username),
};
});

export const invoices = pgTable("invoices", {
id: serial("id").primaryKey(),
userId: integer("user_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
amount: integer("amount").notNull(),
amount: bigint("amount", { mode: "number" }).notNull(),
description: text("description"),
descriptionHash: text("description_hash"),
paymentRequest: text("payment_request").unique().notNull(),
paymentHash: text("payment_hash").unique().notNull(),
preimage: text("preimage"),
Expand All @@ -26,7 +21,6 @@ export const invoices = pgTable("invoices", {
}, (table) => {
return {
userIdIdx: index("user_id_idx").on(table.userId),
paymentHashIdx: index("payment_hash_idx").on(table.paymentHash),
userPaymentHashIdx: index("user_payment_hash_idx").on(table.userId, table.paymentHash),
};
});
33 changes: 11 additions & 22 deletions src/lnurlp.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Event } from "@nostr/tools";
import { validateZapRequest } from "@nostr/tools/nip57";
import { Context, Hono } from "hono";
import { Hono } from "hono";
import { nwc } from "npm:@getalby/sdk";
import { logger } from "../src/logger.ts";
import { BASE_URL, DOMAIN } from "./constants.ts";
Expand All @@ -14,17 +14,10 @@ function getLnurlMetadata(username: string): string {
])
}

async function computeDescriptionHash(content: string): Promise<string> {
const hashBuffer = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(content));
return Array.from(new Uint8Array(hashBuffer))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}

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

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

Expand Down Expand Up @@ -54,15 +47,15 @@ export function createLnurlWellKnownApp(db: DB) {
export function createLnurlApp(db: DB) {
const hono = new Hono();

hono.get("/:username/callback", async (c: Context) => {
hono.get("/:username/callback", async (c) => {
try {
const username = c.req.param("username");
const amount = c.req.query("amount");
const comment = c.req.query("comment") || "";
const payerData = c.req.query("payerdata") ? JSON.parse(c.req.query("payerdata") || "") : null;
const nostr = c.req.query("nostr") ? decodeURIComponent(c.req.query("nostr") || "") : null;

logger.debug("LNURLp callback", { username, amount, comment, payerData, nostr });
logger.debug("LNURLp callback", { username, amount, comment, payer_data: payerData, nostr });

if (!amount) {
throw new Error("No amount provided");
Expand All @@ -79,9 +72,6 @@ export function createLnurlApp(db: DB) {

const description = zapRequest ? zapRequest.content : comment;

const content = zapRequest ? JSON.stringify(nostr) : getLnurlMetadata(username);
const descriptionHash = await computeDescriptionHash(content);

const user = await db.findUser(username);

const nwcClient = new nwc.NWCClient({
Expand All @@ -96,14 +86,13 @@ export function createLnurlApp(db: DB) {
// TODO: payer_data can be improved using nostr worker
payer_data: payerData || undefined,
nostr: zapRequest || undefined,
},
description_hash: descriptionHash,
}
});

const invoice = await db.createInvoice(user.id, transaction);
await db.createInvoice(user.id, transaction);

return c.json({
verify: `${BASE_URL}/lnurlp/${username}/verify/${invoice.identifier}`,
verify: `${BASE_URL}/lnurlp/${username}/verify/${transaction.payment_hash}`,
routes: [],
pr: transaction.invoice,
});
Expand All @@ -112,14 +101,14 @@ export function createLnurlApp(db: DB) {
}
});

hono.get("/:username/verify/:identifier", async (c: Context) => {
hono.get("/:username/verify/:payment_hash", async (c) => {
try {
const username = c.req.param("username");
const identifier = c.req.param("identifier");
const paymentHash = c.req.param("payment_hash");

logger.debug("LNURLp verify", { username, identifier });
logger.debug("LNURLp verify", { username, payment_hash: paymentHash });

const invoice = await db.findInvoice(identifier);
const invoice = await db.findInvoice(paymentHash);

return c.json({
settled: !!invoice.settledAt,
Expand Down
6 changes: 3 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Context, Hono } from "hono";
import { Hono } from "hono";
import { serveStatic } from "hono/deno";
import { secureHeaders } from "hono/secure-headers";
//import { sentry } from "npm:@hono/sentry";
Expand Down Expand Up @@ -30,13 +30,13 @@ hono.route("/.well-known/lnurlp", createLnurlWellKnownApp(db));
hono.route("/lnurlp", createLnurlApp(db));
hono.route("/users", createUsersApp(db, nwcPool));

hono.get("/ping", (c: Context) => {
hono.get("/ping", (c) => {
return c.body("OK");
});

hono.use("/favicon.ico", serveStatic({ path: "./favicon.ico" }));

hono.get("/robots.txt", (c: Context) => {
hono.get("/robots.txt", (c) => {
return c.body("User-agent: *\nDisallow: /", 200);
});

Expand Down
2 changes: 1 addition & 1 deletion src/nwc/nwcPool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class NWCPool {
if (notification.notification_type === "payment_received") {
const transaction = notification.notification
try {
this._db.updateInvoice(userId, transaction)
this._db.markInvoiceSettled(userId, transaction)
await this.publishZap(userId, transaction)
} catch (error) {
logger.error("error processing payment_received notification", { userId, transaction, error });
Expand Down
6 changes: 3 additions & 3 deletions src/users.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Context, Hono } from "hono";
import { Hono } from "hono";
import { DOMAIN } from "./constants.ts";
import { DB } from "./db/db.ts";
import { logger } from "./logger.ts";
Expand All @@ -7,7 +7,7 @@ import { NWCPool } from "./nwc/nwcPool.ts";
export function createUsersApp(db: DB, nwcPool: NWCPool) {
const hono = new Hono();

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

const createUserRequest: { connectionSecret: string; username?: string } =
Expand All @@ -24,7 +24,7 @@ export function createUsersApp(db: DB, nwcPool: NWCPool) {

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

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

return c.json({
lightningAddress,
Expand Down

0 comments on commit 69c7209

Please sign in to comment.