aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2025-02-26 11:59:58 -0800
committerAdam Soltys <[email protected]>2025-02-26 11:59:58 -0800
commit1d541fd97210389f006e65d46ab783afbe82cea8 (patch)
tree66781ea2992fa99198ed1e450cdb9b68acfbc4d9 /lib
parenta14b987a392bc700975b048f37838da08e4ed255 (diff)
downloadcoinos-server-1d541fd97210389f006e65d46ab783afbe82cea8.tar.gz
coinos-server-1d541fd97210389f006e65d46ab783afbe82cea8.zip
payment types
Diffstat (limited to 'lib')
-rw-r--r--lib/invoices.ts17
-rw-r--r--lib/lightning.ts7
-rw-r--r--lib/migrate.ts132
-rw-r--r--lib/payments.ts69
-rw-r--r--lib/types.ts78
-rw-r--r--lib/utils.ts33
6 files changed, 130 insertions, 206 deletions
diff --git a/lib/invoices.ts b/lib/invoices.ts
index 4710beb2..178f793a 100644
--- a/lib/invoices.ts
+++ b/lib/invoices.ts
@@ -2,12 +2,13 @@ import config from "$config";
import { db, g, s } from "$lib/db";
import { request } from "$lib/ecash";
import ln from "$lib/ln";
-import { types } from "$lib/payments";
import { emit } from "$lib/sockets";
import { SATS, bip21, fail, getUser } from "$lib/utils";
import rpc from "@coinos/rpc";
import { v4 } from "uuid";
+import { PaymentType } from "$lib/types";
+
const bc = rpc(config.bitcoin);
const lq = rpc(config.liquid);
@@ -27,7 +28,7 @@ export const generate = async ({ invoice, user }) => {
memo,
memoPrompt,
prompt,
- type = types.lightning,
+ type = PaymentType.lightning,
webhook,
secret,
} = invoice;
@@ -65,7 +66,7 @@ export const generate = async ({ invoice, user }) => {
text = bip21(hash, invoice);
({ hdkeypath: path } = await node.getAddressInfo(hash));
- } else if (type === types.lightning) {
+ } else if (type === PaymentType.lightning) {
let r;
if (bolt11) {
const { id: nodeid } = await ln.getinfo();
@@ -87,7 +88,7 @@ export const generate = async ({ invoice, user }) => {
hash = r.bolt11;
text = r.bolt11;
- } else if (type === types.bolt12) {
+ } else if (type === PaymentType.bolt12) {
const r = await ln.offer({
amount: amount ? `${amount + tip}sat` : "any",
label: id,
@@ -100,17 +101,17 @@ export const generate = async ({ invoice, user }) => {
text = r.bolt12;
await s(`invoice:${r.offer_id}`, id);
- } else if (type === types.bitcoin) {
+ } else if (type === PaymentType.bitcoin) {
address_type ||= "bech32";
hash = await bc.getNewAddress({ address_type });
text = bip21(hash, invoice);
- } else if (type === types.liquid) {
+ } else if (type === PaymentType.liquid) {
address_type ||= "blech32";
hash = await lq.getNewAddress({ address_type });
text = bip21(hash, invoice);
- } else if (type === types.internal) {
+ } else if (type === PaymentType.internal) {
hash = id;
- } else if (type === types.ecash) {
+ } else if (type === PaymentType.ecash) {
hash = id;
text = request(id, amount, memo);
} else {
diff --git a/lib/lightning.ts b/lib/lightning.ts
index f63b57be..4d41d9eb 100644
--- a/lib/lightning.ts
+++ b/lib/lightning.ts
@@ -2,8 +2,9 @@ import { db, g, s } from "$lib/db";
import ln from "$lib/ln";
import { err, warn } from "$lib/logging";
import { handleZap } from "$lib/nostr";
-import { credit, types } from "$lib/payments";
+import { credit } from "$lib/payments";
import { getPayment, getUser } from "$lib/utils";
+import { PaymentType } from "$lib/types";
export async function listenForLightning() {
const inv = await ln.waitanyinvoice((await g("pay_index")) || 0);
@@ -49,7 +50,7 @@ export async function listenForLightning() {
amount: received,
memo: invoice.memo,
ref: preimage,
- type: bolt12 ? types.bolt12 : types.lightning,
+ type: bolt12 ? PaymentType.bolt12 : PaymentType.lightning,
});
} catch (e) {
console.log(e);
@@ -97,7 +98,7 @@ export async function replay(index) {
amount: received,
memo: invoice.memo,
ref: preimage,
- type: bolt12 ? types.bolt12 : types.lightning,
+ type: bolt12 ? PaymentType.bolt12 : PaymentType.lightning,
});
} catch (e) {
console.log(e);
diff --git a/lib/migrate.ts b/lib/migrate.ts
index 8da68cd2..58623ec1 100644
--- a/lib/migrate.ts
+++ b/lib/migrate.ts
@@ -1,132 +1,10 @@
-import { archive, db, g, ga, s } from "$lib/db";
-import { err, l, line, warn } from "$lib/logging";
-import { types } from "$lib/payments";
-
-const queued = {};
-const migrating = {};
+import { g } from "$lib/db";
async function migrate(id) {
- try {
- delete queued[id];
- if (id) id = id.replace(/\s/g, "").toLowerCase();
-
- let user = await g(`user:${id}`);
- if (typeof user === "string") user = await g(`user:${user}`);
- return user;
-
- user = await ga(`user:${id}`);
-
- if (typeof user === "string") {
- if (await g(`user:${user}`)) {
- warn("user already migrated", id);
- s(`user:${id}`, user);
- }
-
- id = user;
- user = await ga(`user:${id}`);
- }
-
- if (user) {
- if (migrating[id]) return;
- migrating[id] = true;
-
- let { username } = user;
- username = username.replace(/\s/g, "").toLowerCase();
- l("migrating", username);
-
- try {
- const multi = await db
- .multi()
- .set(`user:${username}`, id)
- .set(`user:${user.pubkey}`, id)
- .set(`user:${id}`, JSON.stringify(user));
-
- await multi.exec();
-
- await archive.del(`user:${username}`);
- await archive.del(`user:${user.pubkey}`);
- await archive.del(`user:${id}`);
-
- await s(`${id}:contacts`, (await ga(`${id}:contacts`)) || []);
- await archive.del(`${id}:contacts`);
-
- await s(
- `credit:bitcoin:${id}`,
- (await ga(`credit:bitcoin:${id}`)) || 0,
- );
- await archive.del(`credit:bitcoin:${id}`);
-
- await s(
- `credit:lightning:${id}`,
- (await ga(`credit:lightning:${id}`)) || 0,
- );
- await archive.del(`credit:lightning:${id}`);
-
- await s(`${id}:lastlen`, (await ga(`${id}:lastlen`)) || 0);
- await archive.del(`${id}:lastlen`);
- await s(`${id}:cindex`, (await ga(`${id}:cindex`)) || 0);
- await archive.del(`${id}:cindex`);
- await s(`balance:${id}`, (await ga(`balance:${id}`)) || 0);
- await archive.del(`balance:${id}`);
- await s(`pending:${id}`, (await ga(`pending:${id}`)) || 0);
- await archive.del(`pending:${id}`);
-
- let pid;
- while ((pid = await archive.rPop(`${id}:payments`))) {
- const p = await ga(`payment:${pid}`);
- const pos = await db.lPos(`${id}:payments`, pid);
- if (!p || pos) continue;
- if (p.type === types.internal && p.ref) {
- const recipient = await g(`user:${p.ref}`);
- if (!recipient) queued[p.ref] = true;
- }
-
- await db.lPush(`${id}:payments`, pid);
- await s(`payment:${pid}`, p);
-
- await archive.del(`payment:${pid}`);
- }
- await archive.del(`${id}:payments`);
-
- let iid;
- while ((iid = await archive.rPop(`${id}:invoices`))) {
- const inv = await ga(`invoice:${iid}`);
- const pos = await db.lPos(`${id}:invoices`, iid);
- if (!inv || pos) continue;
- await db.lPush(`${id}:invoices`, iid);
- await s(`invoice:${iid}`, inv);
-
- await archive.del(`invoice:${iid}`);
- }
- await archive.del(`${id}:invoices`);
-
- l("done migrating", username);
- } catch (e) {
- warn("failed to migrate", username, e.message, line());
- }
-
- delete migrating[id];
- return user;
- }
- } catch (e) {
- delete migrating[id];
- throw e;
- }
+ const k = id?.replace(/\s/g, "").toLowerCase();
+ let user = await g(`user:${k}`);
+ if (typeof user === "string") user = await g(`user:${user}`);
+ return user;
}
-const keepMigrating = async () => {
- try {
- if (Object.keys(queued).length) {
- const next = Object.keys(queued)[0];
- await migrate(next);
- }
- } catch (e) {
- err("problem migrating", e.message);
- }
-
- setTimeout(keepMigrating, 1000);
-};
-
-keepMigrating();
-
export default migrate;
diff --git a/lib/payments.ts b/lib/payments.ts
index d9833c96..ad39f81c 100644
--- a/lib/payments.ts
+++ b/lib/payments.ts
@@ -26,23 +26,14 @@ import { bech32 } from "bech32";
import got from "got";
import { v4 } from "uuid";
+import { PaymentType } from "$lib/types";
+
const bc = rpc(config.bitcoin);
const lq = rpc(config.liquid);
const { URL } = process.env;
const dust = 547;
-export const types = {
- internal: "internal",
- bitcoin: "bitcoin",
- lightning: "lightning",
- fund: "fund",
- liquid: "liquid",
- ecash: "ecash",
- reconcile: "reconcile",
- bolt12: "bolt12",
-};
-
export const debit = async ({
aid = undefined,
hash,
@@ -50,7 +41,7 @@ export const debit = async ({
fee = 0,
memo = undefined,
user,
- type = types.internal,
+ type = PaymentType.internal,
rate = undefined,
}) => {
amount = parseInt(amount);
@@ -69,7 +60,7 @@ export const debit = async ({
const userLimit = await g("limit");
const frozen =
(await g("hardfreeze")) ||
- ((await g("freeze")) && type !== types.internal) ||
+ ((await g("freeze")) && type !== PaymentType.internal) ||
blacklisted;
if (frozen || (amount > userLimit && !whitelisted) || amount > serverLimit) {
@@ -97,7 +88,7 @@ export const debit = async ({
let iid;
if (invoice) {
- if (invoice.received >= amount && invoice.type !== types.bolt12)
+ if (invoice.received >= amount && invoice.type !== PaymentType.bolt12)
fail("Invoice already paid");
({ id: iid } = invoice);
@@ -119,8 +110,8 @@ export const debit = async ({
if (!amount || amount < 0) fail("Amount must be greater than zero");
let creditType = type;
- if (creditType === types.bolt12) creditType = types.lightning;
- let ourfee: any = [types.bitcoin, types.liquid, types.lightning].includes(
+ if (creditType === PaymentType.bolt12) creditType = PaymentType.lightning;
+ let ourfee: any = [PaymentType.bitcoin, PaymentType.liquid, PaymentType.lightning].includes(
type,
)
? Math.round((amount + fee + tip) * config.fee)
@@ -170,7 +161,7 @@ export const debit = async ({
l(user.username, "sent", type, amount);
//emit(user.id, "payment", p);
- if (![types.lightning, types.bolt12].includes(type)) nwcNotify(p, user);
+ if (![PaymentType.lightning, PaymentType.bolt12].includes(type)) nwcNotify(p, user);
return p;
};
@@ -180,13 +171,13 @@ export const credit = async ({
amount,
memo = "",
ref = "",
- type = types.internal,
+ type = PaymentType.internal,
aid = undefined,
}) => {
amount = parseInt(amount) || 0;
let inv;
- if (type === types.bolt12) {
+ if (type === PaymentType.bolt12) {
const { invoices } = await ln.listinvoices({ invstring: hash });
const { local_offer_id } = invoices[0];
inv = await getInvoice(local_offer_id);
@@ -205,7 +196,7 @@ export const credit = async ({
if (!memo) ({ memo } = inv);
if (memo && memo.length > 5000) fail("memo too long");
if (amount < 0 || tip < 0) fail("Invalid amount");
- if (type === types.internal) amount += tip;
+ if (type === PaymentType.internal) amount += tip;
const user = await getUser(inv.uid);
const { id: uid, currency } = user;
@@ -244,7 +235,7 @@ export const credit = async ({
items: undefined,
};
- if ([types.bitcoin, types.liquid].includes(type)) inv.pending += amount;
+ if ([PaymentType.bitcoin, PaymentType.liquid].includes(type)) inv.pending += amount;
else {
inv.received += amount;
inv.preimage = ref;
@@ -252,7 +243,7 @@ export const credit = async ({
}
let balanceKey = "balance";
- if ([types.bitcoin, types.liquid].includes(type)) {
+ if ([PaymentType.bitcoin, PaymentType.liquid].includes(type)) {
const [txid, vout] = ref.split(":").slice(-2);
p.confirmed = false;
balanceKey = "pending";
@@ -264,8 +255,8 @@ export const credit = async ({
const m = await db.multi();
let creditType = type;
- if (creditType === types.bolt12) creditType = types.lightning;
- if ([types.bitcoin, types.liquid, types.lightning].includes(creditType))
+ if (creditType === PaymentType.bolt12) creditType = PaymentType.lightning;
+ if ([PaymentType.bitcoin, PaymentType.liquid, PaymentType.lightning].includes(creditType))
m.incrBy(`credit:${creditType}:${uid}`, Math.round(amount * config.fee));
m.set(`invoice:${inv.id}`, JSON.stringify(inv))
@@ -359,11 +350,11 @@ export const decode = async (hex) => {
let tx;
try {
tx = await bc.decodeRawTransaction(hex);
- type = types.bitcoin;
+ type = PaymentType.bitcoin;
} catch (e) {
try {
tx = await lq.decodeRawTransaction(hex);
- type = types.liquid;
+ type = PaymentType.liquid;
} catch (e) {
err("invalid hex", hex);
fail("unrecognized tx");
@@ -393,7 +384,7 @@ export const sendOnchain = async (params) => {
await node.walletPassphrase(config[type].walletpass, 300);
({ hex } = await node.signRawTransactionWithWallet(
- type === types.liquid ? await node.blindRawTransaction(hex) : hex,
+ type === PaymentType.liquid ? await node.blindRawTransaction(hex) : hex,
));
}
@@ -406,7 +397,7 @@ export const sendOnchain = async (params) => {
let fee = 0;
let change = 0;
- if (type === types.liquid) {
+ if (type === PaymentType.liquid) {
for (const {
asset,
scriptPubKey: { address, type },
@@ -493,7 +484,7 @@ export const sendKeysend = async ({
fee,
memo,
user,
- type: types.lightning,
+ type: PaymentType.lightning,
});
const r = await ln.keysend({
@@ -551,7 +542,7 @@ export const sendLightning = async ({
fee,
memo,
user,
- type: types.lightning,
+ type: PaymentType.lightning,
});
await db.sAdd("pending", pr);
@@ -612,11 +603,11 @@ export const sendInternal = async ({
const getAddressType = async (a) => {
try {
await bc.getAddressInfo(a);
- return types.bitcoin;
+ return PaymentType.bitcoin;
} catch (e) {
try {
await lq.getAddressInfo(a);
- return types.liquid;
+ return PaymentType.liquid;
} catch (e) {
fail("unrecognized address");
}
@@ -640,14 +631,14 @@ export const build = async ({
if (amount < 0) fail("invalid amount");
const fees: any =
- type === types.liquid
+ type === PaymentType.liquid
? { fastestFee: 0.1, halfHourFee: 0.1, hourFee: 0.1 }
: await fetch(`${api[type]}/fees/recommended`).then((r) => r.json());
fees.hourFee = fees.halfHourFee;
fees.halfHourFee = fees.fastestFee;
- if (type === types.bitcoin) {
+ if (type === PaymentType.bitcoin) {
fees.fastestFee = Math.round(fees.fastestFee * 1.1);
if (fees.fastestFee === fees.halfHourFee) fees.fastestFee++;
if (fees.hourFee === fees.halfHourFee) fees.hourFee--;
@@ -663,7 +654,7 @@ export const build = async ({
let outs = [{ [address]: btc(amount) }];
- if (type === types.liquid)
+ if (type === PaymentType.liquid)
outs = outs.map((o) => ({ ...o, asset: config.liquid.btc }));
let raw = await node.createRawTransaction([], outs, 0, replaceable);
@@ -789,7 +780,7 @@ export const reconcile = async (account, initial = false) => {
if (total > balance) {
const inv = {
memo,
- type: types.reconcile,
+ type: PaymentType.reconcile,
hash,
amount,
uid,
@@ -799,7 +790,7 @@ export const reconcile = async (account, initial = false) => {
await credit({
hash,
amount,
- type: types.reconcile,
+ type: PaymentType.reconcile,
aid: id,
});
} else if (total < balance) {
@@ -809,7 +800,7 @@ export const reconcile = async (account, initial = false) => {
hash: v4(),
memo,
user,
- type: types.reconcile,
+ type: PaymentType.reconcile,
});
}
} catch (e) {
@@ -883,7 +874,7 @@ const reverse = async (p) => {
await db.reverse(
`payment:${p.id}`,
`balance:${p.uid}`,
- `credit:${types.lightning}:${p.uid}`,
+ `credit:${PaymentType.lightning}:${p.uid}`,
`payment:${p.hash}`,
`${p.uid}:payments`,
p.id,
diff --git a/lib/types.ts b/lib/types.ts
new file mode 100644
index 00000000..e878dfb8
--- /dev/null
+++ b/lib/types.ts
@@ -0,0 +1,78 @@
+export interface User {
+ about: string;
+ admin: string;
+ anon: string;
+ autowithdraw: string;
+ balance: string;
+ banner: string;
+ currencies: string;
+ currency: string;
+ destination: string;
+ display: string;
+ email: string;
+ fiat: string;
+ followers: string;
+ follows: string;
+ haspin: string;
+ hasprinter: string;
+ hidepay: string;
+ id: string;
+ index: string;
+ keys: string;
+ language: string;
+ linked: string;
+ locktime: string;
+ memoPrompt: string;
+ nip5: string;
+ notify: string;
+ npub: string;
+ nsec: string;
+ nwc: string;
+ payments: string;
+ picture: string;
+ profile: string;
+ prompt: string;
+ pubkey: string;
+ push: string;
+ reserve: string;
+ seed: string;
+ shopifyStore: string;
+ shopifyToken: string;
+ theme: string;
+ threshold: string;
+ twofa: string;
+ username: string;
+ verified: string;
+}
+
+export enum PaymentType {
+ internal = "internal",
+ bitcoin = "bitcoin",
+ lightning = "lightning",
+ fund = "fund",
+ liquid = "liquid",
+ ecash = "ecash",
+ reconcile = "reconcile",
+ bolt12 = "bolt12",
+}
+
+export interface Payment {
+ id: string;
+ aid: string;
+ amount: number;
+ fee: number;
+ hash: string;
+ hex?: string;
+ ourfee: number;
+ memo?: string;
+ iid: string;
+ uid: string;
+ confirmed: boolean;
+ rate: number;
+ currency: string;
+ type: PaymentType;
+ ref: string;
+ tip?: number;
+ created: number;
+ user?: User;
+}
diff --git a/lib/utils.ts b/lib/utils.ts
index 79b95e62..018dc92e 100644
--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -1,9 +1,6 @@
import config from "$config";
-import { g, s } from "$lib/db";
+import { g } from "$lib/db";
import locales from "$lib/locales/index";
-import migrate from "$lib/migrate";
-import { bytesToHex, randomBytes } from "@noble/hashes/utils";
-import { getPublicKey } from "nostr";
const { URL } = process.env;
@@ -51,32 +48,10 @@ export const bip21 = (address, { amount, memo, tip, type }) => {
};
export const getUser = async (username, fields = undefined) => {
- let update;
if (username === "undefined") fail("invalid user");
- const user = await migrate(username);
- if (user && !user.anon && !user.nwc) {
- user.nwc = bytesToHex(randomBytes(32));
- await s(getPublicKey(user.nwc), user.id);
- update = true;
- }
-
- if (user?.profile) {
- user.picture = `https://coinos.io/api/public/${user.profile}.webp`;
- delete user.profile;
-
- if (user.banner) {
- user.banner = `https://coinos.io/api/public/${user.banner}.webp`;
- }
- update = true;
- }
-
- if (user?.address) {
- user.about = user.address;
- delete user.address;
- update = true;
- }
-
- if (update) s(`user:${user.id}`, user);
+ const k = username?.replace(/\s/g, "").toLowerCase();
+ let user = await g(`user:${k}`);
+ if (typeof user === "string") user = await g(`user:${user}`);
return fields && user ? pick(user, fields) : user;
};