aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2025-01-21 23:28:00 -0800
committerAdam Soltys <[email protected]>2025-01-21 23:28:00 -0800
commit9662c2754834599a1074d12565279674c20af467 (patch)
tree1d77aaedf19b035378236305af582c903e025467
parenteb4e16b5599e67a65962db2a07629dfdcdda57dc (diff)
downloadcoinos-server-9662c2754834599a1074d12565279674c20af467.tar.gz
coinos-server-9662c2754834599a1074d12565279674c20af467.zip
notes and stuff
-rw-r--r--index.ts1
-rw-r--r--lib/lightning.ts12
-rw-r--r--lib/nostr.ts38
-rw-r--r--lib/nwc.ts2
-rw-r--r--lib/payments.ts2
-rw-r--r--lib/strfry.ts39
-rw-r--r--routes/nostr.ts109
-rw-r--r--routes/payments.ts1
-rwxr-xr-xserverbin16960 -> 17208 bytes
-rw-r--r--server.c236
10 files changed, 275 insertions, 165 deletions
diff --git a/index.ts b/index.ts
index 9cc0a371..031584b0 100644
--- a/index.ts
+++ b/index.ts
@@ -53,6 +53,7 @@ app.get("/profile/:profile", nostr.profile);
app.get("/:pubkey/count", nostr.count);
app.get("/:pubkey/followers", nostr.followers);
app.get("/:pubkey/follows", nostr.follows);
+app.get("/:pubkey/events", nostr.events);
app.get("/event/:id", nostr.event);
app.post("/event", auth, nostr.publish);
app.post("/parseEvent", nostr.parse);
diff --git a/lib/lightning.ts b/lib/lightning.ts
index 19f6949d..3888a405 100644
--- a/lib/lightning.ts
+++ b/lib/lightning.ts
@@ -3,7 +3,7 @@ import ln from "$lib/ln";
import { err, warn } from "$lib/logging";
import { handleZap } from "$lib/nostr";
import { credit, types } from "$lib/payments";
-import { getPayment } from "$lib/utils";
+import { getPayment, getUser } from "$lib/utils";
export async function listenForLightning() {
const inv = await ln.waitanyinvoice((await g("pay_index")) || 0);
@@ -35,7 +35,10 @@ export async function listenForLightning() {
if (invoice?.memo) {
try {
- if (JSON.parse(description).kind === 9734) handleZap(inv);
+ if (JSON.parse(description).kind === 9734) {
+ const { pubkey } = await getUser(invoice.uid);
+ handleZap(inv, pubkey);
+ }
} catch (e) {
if (!e.message.includes("Unexpected"))
warn("failed to handle zap", e.message);
@@ -81,7 +84,10 @@ export async function replay(index) {
if (invoice?.memo) {
try {
- if (JSON.parse(description).kind === 9734) handleZap(inv);
+ if (JSON.parse(description).kind === 9734) {
+ const { pubkey } = await getUser(invoice.uid);
+ handleZap(inv, pubkey);
+ }
} catch (e) {
if (!e.message.includes("Unexpected"))
warn("failed to handle zap", e.message);
diff --git a/lib/nostr.ts b/lib/nostr.ts
index 190873e2..21c254e3 100644
--- a/lib/nostr.ts
+++ b/lib/nostr.ts
@@ -33,7 +33,7 @@ export async function send(ev, url = config.nostr) {
r.close();
}
-export async function handleZap(invoice) {
+export async function handleZap(invoice, sender = undefined) {
try {
const pubkey = serverPubkey;
const sk = nip19.decode(config.nostrKey).data as Uint8Array;
@@ -59,6 +59,10 @@ export async function handleZap(invoice) {
fail("Expected none or 1 e tags");
}
+ const atags = zapreq.tags.filter(
+ (t) => t?.length && t.length >= 2 && t[0] === "a",
+ );
+
const relays_tag = zapreq.tags.find(
(t) => t?.length && t.length >= 2 && t[0] === "relays",
);
@@ -69,6 +73,7 @@ export async function handleZap(invoice) {
const relays = relays_tag.slice(1).filter((r) => r?.startsWith("ws"));
const etag = etags.length > 0 && etags[0];
+ const atag = atags.length > 0 && atags[0];
const ptag = ptags[0];
const kind = 9735;
@@ -77,6 +82,8 @@ export async function handleZap(invoice) {
const tags = [ptag];
if (etag) tags.push(etag);
+ if (atag) tags.push(atag);
+ if (sender) tags.push(["P", sender]);
tags.push(["bolt11", invoice.bolt11]);
tags.push(["description", invoice.description]);
@@ -129,11 +136,10 @@ export const getProfile = async (pubkey) => {
let profile = await g(`profile:${pubkey}`);
if (profile) return profile;
- const filter = { authors: [pubkey], kinds: [0] };
- const events = await scan(filter);
+ const event = await get({ authors: [pubkey], kinds: [0] });
- if (events.length) {
- profile = JSON.parse(events[0].content);
+ if (event) {
+ profile = JSON.parse(event.content);
} else {
profile = anon(pubkey);
}
@@ -191,3 +197,25 @@ export const getNostrUser = async (key) => {
return pick(user, fields);
};
+
+export const q = async (f) => {
+ const events = await scan(f);
+ const k = JSON.stringify(f).replace(/[^a-zA-Z0-9]/g, "");
+ const since = await g(`${k}:since}`);
+ if (since) f.since = since;
+
+ const p = sync("wss://relay.primal.net", f).then(async () => {
+ const r = await scan(f);
+ if (r.length) await s(`${k}:since`, r[r.length - 1].created_at);
+ return r;
+ });
+
+ if (events.length > 0) return events;
+ return p;
+};
+
+export const get = async (f) => {
+ f.limit = 1;
+ const events = await q(f);
+ return events[0];
+};
diff --git a/lib/nwc.ts b/lib/nwc.ts
index 82c9870d..434e0774 100644
--- a/lib/nwc.ts
+++ b/lib/nwc.ts
@@ -113,7 +113,7 @@ const handle = (method, params, user, ev) =>
inv.payment_preimage = preimage;
inv.paid_at = Math.floor(Date.now() / 1000);
try {
- await handleZap(inv);
+ await handleZap(inv, pubkey);
} catch (e) {
console.log("zap receipt failed", e);
}
diff --git a/lib/payments.ts b/lib/payments.ts
index 5f818ae4..1468a198 100644
--- a/lib/payments.ts
+++ b/lib/payments.ts
@@ -567,7 +567,7 @@ export const sendInternal = async ({
const inv = invoices[0];
inv.payment_preimage = p.id;
inv.paid_at = Math.floor(Date.now() / 1000);
- handleZap(inv).catch(console.log);
+ handleZap(inv, sender.pubkey).catch(console.log);
}
return p;
diff --git a/lib/strfry.ts b/lib/strfry.ts
index 231e3533..926ca429 100644
--- a/lib/strfry.ts
+++ b/lib/strfry.ts
@@ -1,9 +1,10 @@
import { randomUUID } from "crypto";
import { connect, createServer } from "net";
+import type { Event } from "nostr-tools";
-const exec = async (cmd) =>
+const exec = async (cmd: string, data = ""): Promise<Event[]> =>
new Promise((resolve, reject) => {
- const c = `/app/strfry ${cmd} 2>/dev/null`;
+ const c = `${cmd} 2>/dev/null`;
const id = randomUUID();
const resultSocketPath = `/sockets/result_${id}`;
@@ -11,8 +12,8 @@ const exec = async (cmd) =>
let resultBuffer = "";
const results = [];
- socket.on("data", (data) => {
- resultBuffer += data.toString();
+ socket.on("data", (chunk) => {
+ resultBuffer += chunk.toString();
const parts = resultBuffer.split("\n");
for (let i = 0; i < parts.length - 1; i++) {
@@ -20,7 +21,7 @@ const exec = async (cmd) =>
const parsedObject = JSON.parse(parts[i]);
results.push(parsedObject);
} catch (e) {
- console.error(parts[i]);
+ console.error("Failed to parse:", parts[i]);
}
}
@@ -38,7 +39,8 @@ const exec = async (cmd) =>
resultServer.listen(resultSocketPath, () => {
const controlClient = connect("/sockets/ctrl", () => {
- const message = `${resultSocketPath} ${c}\n`;
+ // Send the command and data payload
+ const message = `${resultSocketPath} ${c}\n${data}\n`;
controlClient.write(message);
controlClient.end();
});
@@ -53,7 +55,26 @@ const exec = async (cmd) =>
});
});
-export const count = (f) => exec(`scan --count '${JSON.stringify(f)}'\n`);
-export const scan = (f) => exec(`scan '${JSON.stringify(f)}'\n`);
+// const filter2params = (f) => {
+// let r = "";
+//
+// const { limit, since, until } = f;
+//
+// if (limit) r += `-l ${limit} `;
+// if (since) r += `-s ${since} `;
+// if (until) r += `-u ${until} `;
+//
+// for (const k of f.kinds || []) r += `-k ${k} `;
+// for (const a of f.authors || []) r += `-a ${a} `;
+// for (const e of f["#e"] || []) r += `-e ${e} `;
+// for (const p of f["#p"] || []) r += `-p ${p} `;
+//
+// return r;
+// };
+
+export const load = (data) => exec(`echo '${data}' | /app/strfry import --no-verify\n`);
+export const count = (f) =>
+ exec(`/app/strfry scan --count '${JSON.stringify(f)}'\n`);
+export const scan = (f) => exec(`/app/strfry scan '${JSON.stringify(f)}'\n`);
export const sync = (r, f) =>
- exec(`sync ${r} --dir down --filter '${JSON.stringify(f)}'\n`);
+ exec(`/app/strfry sync ${r} --dir down --filter '${JSON.stringify(f)}'\n`);
diff --git a/routes/nostr.ts b/routes/nostr.ts
index 61e23c30..0bfe9970 100644
--- a/routes/nostr.ts
+++ b/routes/nostr.ts
@@ -3,35 +3,31 @@ import { db } from "$lib/db";
import ln from "$lib/ln";
import {
anon,
+ get,
getCount,
getNostrUser,
getProfile,
getRelays,
+ q,
send,
serverPubkey,
} from "$lib/nostr";
import { parseContent } from "$lib/notes";
-import { scan, sync } from "$lib/strfry";
+import { load, scan, sync } from "$lib/strfry";
import { bail, fail, fields, getUser } from "$lib/utils";
import got from "got";
import type { Event } from "nostr-tools";
+import { verifyEvent } from "nostr-tools";
import { decode } from "nostr-tools/nip19";
import type { ProfilePointer } from "nostr-tools/nip19";
import { getZapEndpoint, makeZapRequest } from "nostr-tools/nip57";
-import { Relay } from "nostr-tools/relay";
export default {
async event(req, res) {
let { id } = req.params;
if (id.startsWith("nevent")) id = decode(id).data.id;
if (id.startsWith("note")) id = decode(id).data;
- const filter = { ids: [id] };
- let events = await scan(filter);
-
- if (!events.length) {
- await sync("wss://relay.primal.net", filter);
- events = await scan(filter);
- }
+ const events = await q({ ids: [id] });
if (!events.length) return bail(res, "event not found");
@@ -56,41 +52,22 @@ export default {
async thread(req, res) {
try {
const { id } = req.params;
- const relay = await Relay.connect("wss://relay.primal.net");
- console.log("ID", id);
- const event: Event = await new Promise((resolve) => {
- const sub = relay.subscribe([{ ids: [id] }], {
- async onevent(event) {
- resolve(event);
- },
- oneose() {
- sub.close();
- },
- });
- });
+ const event = await get({ ids: [id] });
- const thread = [];
+ const rootId = event.tags.find(
+ (tag) => tag[0] === "e" && tag[3] === "root",
+ )?.[1];
- const root =
- event.tags.find((tag) => tag[0] === "e" && tag[3] === "root")?.[1] ||
- event.id;
+ let root;
+ if (rootId) root = await get({ ids: [rootId] });
+ else root = event;
- await new Promise<void>((resolve) => {
- const sub = relay.subscribe([{ kinds: [1], "#e": [root] }], {
- async onevent(event) {
- thread.push(event);
- },
- oneose() {
- sub.close();
- resolve();
- },
- });
- });
+ const thread = [root, ...(await q({ kinds: [1], "#e": [root.id] }))];
- for (const e of thread) {
+ for (const t of thread) {
+ const e = t as any;
e.author = await getNostrUser(e.pubkey);
- console.log("AUTHOR", e.author);
e.parts = parseContent(e);
e.names = {};
for (const { type, value } of e.parts) {
@@ -135,13 +112,10 @@ export default {
let user;
if (pubkey) {
try {
- user = await getUser(pubkey, fields);
- if (!user) user = await getProfile(pubkey);
- user.pubkey = pubkey;
+ user = await getNostrUser(pubkey);
+ zaps.push({ amount, user });
} catch (e) {}
}
-
- zaps.push({ amount, user });
}
res.send(zaps.filter((z) => z.amount > 0));
@@ -155,20 +129,10 @@ export default {
try {
const { event } = req.body;
const { pubkey } = req.user;
- const { write: relays } = await getRelays(pubkey);
- if (!relays.includes(config.nostr)) relays.push(config.nostr);
-
- let ok;
- for (const url of relays) {
- try {
- await send(event, url);
- ok = true;
- } catch (e) {
- console.log("failed to publish to", url);
- }
- }
-
- if (!ok) fail("failed to publish");
+ // const { write: relays } = await getRelays(pubkey);
+ // if (!relays.includes(config.nostr)) relays.push(config.nostr);
+ if (!verifyEvent(event)) fail("Invalid event");
+ await load(JSON.stringify(event));
if (event.kind === 3) db.del(`${pubkey}:follows`);
res.send({});
@@ -223,6 +187,33 @@ export default {
}
},
+ async events(req, res) {
+ const {
+ params: { pubkey },
+ } = req;
+ try {
+ const events = await q({ kinds: [1], authors: [pubkey], limit: 20 });
+
+ for (const v of events) {
+ const e = v as any;
+ e.author = await getNostrUser(e.pubkey);
+ e.parts = parseContent(e);
+ e.names = {};
+ for (const { type, value } of e.parts) {
+ if (type.includes("nprofile") || type.includes("npub")) {
+ const { name } = await getProfile(value.pubkey);
+ e.names[value.pubkey] = name;
+ }
+ }
+ }
+
+ res.send(events);
+ } catch (e) {
+ console.log(e);
+ bail(res, e.message);
+ }
+ },
+
async followers(req, res) {
const {
params: { pubkey },
@@ -306,7 +297,8 @@ export default {
async zapRequest(req, res) {
try {
- const { amount, pubkey, id } = req.body;
+ const { amount, id } = req.body;
+ const { pubkey } = await get({ ids: [id] });
const event = await makeZapRequest({
profile: pubkey,
event: id,
@@ -328,6 +320,7 @@ export default {
const pubkey = event.tags.find((t) => t[0] === "p")[1];
const content = JSON.stringify(await getProfile(pubkey));
const callback = await getZapEndpoint({ content } as Event);
+ if (!callback || callback === "null") fail("Lightning address not found");
const encodedEvent = encodeURI(JSON.stringify(event));
const url = `${callback}?amount=${amount}&nostr=${encodedEvent}`;
diff --git a/routes/payments.ts b/routes/payments.ts
index 1ff0be05..93e5d3da 100644
--- a/routes/payments.ts
+++ b/routes/payments.ts
@@ -40,6 +40,7 @@ export default {
async create(req, res) {
const { body, user } = req;
+
let { amount, hash, fee, fund, memo, payreq } = body;
const balance = await g(`balance:${user.id}`);
diff --git a/server b/server
index 76b9e336..601f702a 100755
--- a/server
+++ b/server
Binary files differ
diff --git a/server.c b/server.c
index 8522785b..8f65a793 100644
--- a/server.c
+++ b/server.c
@@ -6,119 +6,179 @@
#include <unistd.h>
#define SOCKET_PATH "/sockets/ctrl"
-#define BUFFER_SIZE 262144
+#define INITIAL_BUFFER_SIZE 1024
-void handle_client(const char *result_socket, const char *command);
+void handle_client(const char *result_socket, const char *command, const char *data);
int main() {
- int server_fd, client_fd;
- struct sockaddr_un server_addr;
- char buffer[BUFFER_SIZE];
+ int server_fd, client_fd;
+ struct sockaddr_un server_addr;
- unlink(SOCKET_PATH);
+ // Unlink the socket file in case it exists
+ unlink(SOCKET_PATH);
- server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
- if (server_fd == -1) {
- perror("socket failed");
- exit(EXIT_FAILURE);
- }
+ server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (server_fd == -1) {
+ perror("socket failed");
+ exit(EXIT_FAILURE);
+ }
- memset(&server_addr, 0, sizeof(struct sockaddr_un));
- server_addr.sun_family = AF_UNIX;
- strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
+ memset(&server_addr, 0, sizeof(struct sockaddr_un));
+ server_addr.sun_family = AF_UNIX;
+ strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
- if (bind(server_fd, (struct sockaddr *)&server_addr,
- sizeof(struct sockaddr_un)) == -1) {
- perror("bind failed");
- close(server_fd);
- exit(EXIT_FAILURE);
- }
+ if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_un)) == -1) {
+ perror("bind failed");
+ close(server_fd);
+ exit(EXIT_FAILURE);
+ }
- if (listen(server_fd, 5) == -1) {
- perror("listen failed");
- close(server_fd);
- exit(EXIT_FAILURE);
- }
+ if (listen(server_fd, 5) == -1) {
+ perror("listen failed");
+ close(server_fd);
+ exit(EXIT_FAILURE);
+ }
- printf("Server listening on %s\n", SOCKET_PATH);
+ printf("Server listening on %s\n", SOCKET_PATH);
- while (1) {
+ while (1) {
+ client_fd = accept(server_fd, NULL, NULL);
+ if (client_fd == -1) {
+ perror("accept failed");
+ continue;
+ }
- client_fd = accept(server_fd, NULL, NULL);
- if (client_fd == -1) {
- perror("accept failed");
- continue;
- }
+ size_t buffer_size = INITIAL_BUFFER_SIZE;
+ char *buffer = malloc(buffer_size);
+ if (!buffer) {
+ perror("malloc failed");
+ close(client_fd);
+ continue;
+ }
- memset(buffer, 0, BUFFER_SIZE);
- ssize_t num_bytes = read(client_fd, buffer, BUFFER_SIZE - 1);
- if (num_bytes > 0) {
+ size_t total_bytes_read = 0;
+ ssize_t num_bytes;
- buffer[num_bytes] = '\0';
+ while ((num_bytes = read(client_fd, buffer + total_bytes_read, buffer_size - total_bytes_read - 1)) > 0) {
+ total_bytes_read += num_bytes;
- printf("Received: %s\n", buffer);
+ // Expand buffer if necessary
+ if (total_bytes_read >= buffer_size - 1) {
+ buffer_size *= 2;
+ char *new_buffer = realloc(buffer, buffer_size);
+ if (!new_buffer) {
+ perror("realloc failed");
+ free(buffer);
+ close(client_fd);
+ continue;
+ }
+ buffer = new_buffer;
+ }
+ }
- char result_socket[BUFFER_SIZE], command[BUFFER_SIZE];
- if (sscanf(buffer, "%s %[^\n]", result_socket, command) == 2) {
- printf("Result socket: %s\n", result_socket);
- printf("Command: %s\n", command);
+ if (num_bytes == -1) {
+ perror("read failed");
+ free(buffer);
+ close(client_fd);
+ continue;
+ }
- handle_client(result_socket, command);
- } else {
- fprintf(stderr, "Invalid input format\n");
- }
- }
+ buffer[total_bytes_read] = '\0'; // Null-terminate the string
+
+ printf("Received: %s\n", buffer);
+
+ // Split message into result_socket, command, and data
+ char *result_socket = strtok(buffer, " ");
+ char *command = strtok(NULL, "\n");
+ char *data = strtok(NULL, "");
- close(client_fd);
- }
+ if (result_socket && command) {
+ printf("Result socket: %s\n", result_socket);
+ printf("Command: %s\n", command);
+ if (data) {
+ printf("Data: %s\n", data);
+ }
+ handle_client(result_socket, command, data);
+ } else {
+ fprintf(stderr, "Invalid input format\n");
+ }
+
+ free(buffer);
+ close(client_fd);
+ }
- close(server_fd);
- unlink(SOCKET_PATH);
- return 0;
+ close(server_fd);
+ unlink(SOCKET_PATH);
+ return 0;
}
-void handle_client(const char *result_socket, const char *command) {
- pid_t pid = fork();
+void handle_client(const char *result_socket, const char *command, const char *data) {
+ pid_t pid = fork();
- if (pid == 0) {
+ if (pid == 0) {
+ int result_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (result_fd == -1) {
+ perror("result socket failed");
+ exit(EXIT_FAILURE);
+ }
- int result_fd = socket(AF_UNIX, SOCK_STREAM, 0);
- if (result_fd == -1) {
- perror("result socket failed");
- exit(EXIT_FAILURE);
- }
+ struct sockaddr_un result_addr;
+ memset(&result_addr, 0, sizeof(struct sockaddr_un));
+ result_addr.sun_family = AF_UNIX;
+ strncpy(result_addr.sun_path, result_socket, sizeof(result_addr.sun_path) - 1);
- struct sockaddr_un result_addr;
- memset(&result_addr, 0, sizeof(struct sockaddr_un));
- result_addr.sun_family = AF_UNIX;
- strncpy(result_addr.sun_path, result_socket,
- sizeof(result_addr.sun_path) - 1);
+ if (connect(result_fd, (struct sockaddr *)&result_addr, sizeof(struct sockaddr_un)) == -1) {
+ perror("connect to result socket failed");
+ close(result_fd);
+ exit(EXIT_FAILURE);
+ }
- if (connect(result_fd, (struct sockaddr *)&result_addr,
- sizeof(struct sockaddr_un)) == -1) {
- perror("connect to result socket failed");
- close(result_fd);
- exit(EXIT_FAILURE);
- }
+ int pipe_fd[2];
+ if (pipe(pipe_fd) == -1) {
+ perror("pipe failed");
+ close(result_fd);
+ exit(EXIT_FAILURE);
+ }
- FILE *cmd_output = popen(command, "r");
- if (cmd_output == NULL) {
- perror("popen failed");
- close(result_fd);
- exit(EXIT_FAILURE);
- }
+ pid_t cmd_pid = fork();
+ if (cmd_pid == 0) {
+ // Child process: run the command
+ dup2(pipe_fd[0], STDIN_FILENO); // Redirect stdin
+ close(pipe_fd[0]);
+ close(pipe_fd[1]);
+ close(result_fd);
- char output_buffer[BUFFER_SIZE];
- while (fgets(output_buffer, sizeof(output_buffer), cmd_output) != NULL) {
- write(result_fd, output_buffer, strlen(output_buffer));
- }
+ execlp("/bin/sh", "sh", "-c", command, (char *)NULL);
+ perror("execlp failed");
+ exit(EXIT_FAILURE);
+ }
- pclose(cmd_output);
- shutdown(result_fd, SHUT_WR);
- close(result_fd);
- unlink(result_socket);
- exit(0);
- } else if (pid < 0) {
- perror("fork failed");
- }
+ // Parent process: send data and collect output
+ close(pipe_fd[0]); // Close read end of the pipe
+
+ if (data) {
+ write(pipe_fd[1], data, strlen(data));
+ }
+ close(pipe_fd[1]); // Close write end of the pipe
+
+ FILE *cmd_output = fdopen(result_fd, "w");
+ if (!cmd_output) {
+ perror("fdopen failed");
+ close(result_fd);
+ exit(EXIT_FAILURE);
+ }
+
+ char output_buffer[INITIAL_BUFFER_SIZE];
+ while (fgets(output_buffer, sizeof(output_buffer), cmd_output) != NULL) {
+ write(result_fd, output_buffer, strlen(output_buffer));
+ }
+
+ fclose(cmd_output);
+ shutdown(result_fd, SHUT_WR);
+ close(result_fd);
+ unlink(result_socket);
+ exit(0);
+ } else if (pid < 0) {
+ perror("fork failed");
+ }
}