diff options
| author | Adam Soltys <[email protected]> | 2025-02-26 11:59:58 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2025-02-26 11:59:58 -0800 |
| commit | 1d541fd97210389f006e65d46ab783afbe82cea8 (patch) | |
| tree | 66781ea2992fa99198ed1e450cdb9b68acfbc4d9 /lib | |
| parent | a14b987a392bc700975b048f37838da08e4ed255 (diff) | |
| download | coinos-server-1d541fd97210389f006e65d46ab783afbe82cea8.tar.gz coinos-server-1d541fd97210389f006e65d46ab783afbe82cea8.zip | |
payment types
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/invoices.ts | 17 | ||||
| -rw-r--r-- | lib/lightning.ts | 7 | ||||
| -rw-r--r-- | lib/migrate.ts | 132 | ||||
| -rw-r--r-- | lib/payments.ts | 69 | ||||
| -rw-r--r-- | lib/types.ts | 78 | ||||
| -rw-r--r-- | lib/utils.ts | 33 |
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; }; |
