diff options
| author | Adam Soltys <[email protected]> | 2022-09-14 12:31:24 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2022-09-14 12:31:24 -0700 |
| commit | ed261ce59cae16c71868224455a4f8ca522d3212 (patch) | |
| tree | 4cb95d14ec9edb3f1cba7e3a59f3f42ece3022cd | |
| parent | aa6ce918b043f37bed35b6f5c3106e5251e626b7 (diff) | |
| parent | 59d7dcd4f97a63c2d572f41c908b342114701dc4 (diff) | |
| download | coinos-server-ed261ce59cae16c71868224455a4f8ca522d3212.tar.gz coinos-server-ed261ce59cae16c71868224455a4f8ca522d3212.zip | |
Merge branch 'master' into prod
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | docker-compose.yml | 7 | ||||
| -rw-r--r-- | lib/sendInternal.js | 217 | ||||
| -rw-r--r-- | package.json | 4 | ||||
| -rw-r--r-- | prod.yml | 1 | ||||
| -rw-r--r-- | routes/liquid/receive.js | 6 | ||||
| -rw-r--r-- | routes/lnurl.js | 13 | ||||
| -rw-r--r-- | routes/send.js | 220 | ||||
| -rw-r--r-- | sampleconfig/lnurl.js | 46 |
10 files changed, 283 insertions, 234 deletions
@@ -19,3 +19,4 @@ build.json build.sh watcher.sh logs +docker-compose.mainnet.yml @@ -19,7 +19,7 @@ cp fx.sample fx docker network create net --gateway 172.18.0.1 --subnet 172.18.0.0/16 docker run -it -v $(pwd):/app --entrypoint pnpm asoltys/coinos-server i docker-compose up -d -docker exec -i mariadb mysql -u root -ppassword < db/schema.sql +docker exec -i maria mysql -u root -ppassword < db/schema.sql docker exec -it bitcoin bitcoin-cli createwallet coinos docker exec -it bitcoin bitcoin-cli rescanblockchain docker exec -it bitcoin bitcoin-cli generatetoaddress 500 $(docker exec -it bitcoin bitcoin-cli getnewaddress "" "p2sh-segwit") diff --git a/docker-compose.yml b/docker-compose.yml index 49f143f0..6b430e35 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,6 @@ services: - '8085:8085' environment: NODE_ENV: development - # NODE_OPTIONS: --openssl-legacy-provider volumes: - ../coinos-ui:/app networks: @@ -23,14 +22,18 @@ services: - lnd container_name: app image: gcr.io/coinos-326717/github.com/coinos/coinos-server:prod + command: ["pnpm", "dev"] ports: - '3118:3118' - '3119:3119' environment: + NODE_OPTIONS: "--es-module-specifier-resolution=node --experimental-modules --loader ./loader.js --openssl-legacy-provider" NODE_ENV: development volumes: - ./:/app - ./logs:/logs + extra_hosts: + - host.docker.internal:host-gateway networks: default: ipv4_address: 172.18.0.22 @@ -82,7 +85,7 @@ services: default: ipv4_address: 172.18.0.31 maria: - container_name: mariadb + container_name: maria image: mariadb:10.6.1 restart: always environment: diff --git a/lib/sendInternal.js b/lib/sendInternal.js new file mode 100644 index 00000000..80701ca3 --- /dev/null +++ b/lib/sendInternal.js @@ -0,0 +1,217 @@ +import config from "$config"; +import db from "$db"; +import { emit } from "$lib/sockets"; +import store from "$lib/store"; +import { callWebhook } from "$lib/webhooks"; +import axios from "axios"; +import { v4 } from "uuid"; +import { notify } from "$lib/notifications"; +import { sendLiquid } from "$routes/liquid/send"; +import { l, err } from "$lib/logging"; +import { fail } from "$lib/utils"; + +export default async ({ + amount, + address, + payreq, + unconfidential, + asset, + memo, + username, +}, hostname, user) => { + amount = parseInt(amount); + + if (!asset) asset = config.liquid.btcasset; + + if (!amount || amount < 0) fail("Amount must be greater than zero"); + + return db.transaction(async transaction => { + let account; + if (user.account.asset === asset && !user.account.pubkey) { + account = await db.Account.findOne({ + where: { + id: user.account.id, + pubkey: null + }, + lock: transaction.LOCK.UPDATE, + transaction + }); + } else { + account = await db.Account.findOne({ + where: { + user_id: user.id, + asset, + pubkey: null + }, + lock: transaction.LOCK.UPDATE, + transaction + }); + } + + if (!account) throw new Error("Account not found"); + + if (account.balance < amount) { + throw new Error("Insufficient funds"); + } + + let fee = 0; + + await account.decrement({ balance: amount }, { transaction }); + await account.reload({ transaction }); + + let params = { + amount: -amount, + account_id: account.id, + memo, + user_id: user.id, + rate: store.rates[user.currency], + currency: user.currency, + confirmed: true, + hash: `#${v4().substr(0, 6)} ${ + username ? `Payment to ${username}` : "Internal Transfer" + }`, + network: "COINOS" + }; + + if (!username) { + l("creating redeemable payment"); + params.redeemcode = v4(); + params.hash = `${hostname}/redeem/${params.redeemcode}`; + } + + let payment = await db.Payment.create(params, { transaction }); + + payment = payment.get({ plain: true }); + payment.account = account.get({ plain: true }); + + l("sent internal", user.username, -payment.amount); + + emit(user.username, "payment", payment); + emit(user.username, "account", account); + + if (username) { + let recipient = await db.User.findOne( + { + where: { username }, + include: { + model: db.Account, + as: "account" + } + }, + { transaction } + ); + + params = { + where: { user_id: recipient.id }, + order: [["id", "DESC"]] + }; + if (address) params.where.address = address; + else if (payreq) params.where.text = payreq; + let invoice = (address || payreq) && (await db.Invoice.findOne(params)); + + let a2; + let acc = { + user_id: recipient.id, + asset, + pubkey: null + }; + + if (recipient.account.asset === asset && !recipient.account.pubkey) + a2 = recipient.account; + else { + a2 = await db.Account.findOne({ + where: acc, + lock: transaction.LOCK.UPDATE, + transaction + }); + } + + if (a2) { + await a2.increment({ balance: amount }, { transaction }); + await a2.reload({ transaction }); + } else { + let name = asset.substr(0, 6); + let domain; + let ticker = asset.substr(0, 3).toUpperCase(); + let precision = 8; + + if (store.assets[asset]) { + ({ domain, ticker, precision, name } = store.assets[asset]); + } else { + const existing = await db.Account.findOne({ + where: { + asset + }, + order: [["id", "ASC"]], + limit: 1, + lock: transaction.LOCK.UPDATE, + transaction + }); + + if (existing) { + ({ domain, ticker, precision, name } = existing); + } + } + + acc = { ...acc, ...{ domain, ticker, precision, name } }; + acc.balance = amount; + acc.pending = 0; + acc.network = "liquid"; + a2 = await db.Account.create(acc, { transaction }); + } + + let spread = user.username === "coinos" ? 1.02 : 1; + amount = Math.round(amount / spread); + + params = { + amount, + account_id: a2.id, + user_id: recipient.id, + rate: store.rates[recipient.currency] * spread, + currency: recipient.currency, + confirmed: true, + hash: `#${v4().substr(0, 6)} Payment from ${user.username}`, + memo, + network: "COINOS", + received: true + }; + + if (invoice) { + params.invoice_id = invoice.id; + + let c = store.convert[invoice.text]; + if (c) { + l( + "internal payment detected for conversion", + invoice.text, + c.address, + recipient.username + ); + + recipient.account = a2; + + sendLiquid({ + address: c.address, + amount: amount - 100, + user: recipient, + limit: amount + }).catch(console.log); + } + } + + let p2 = await db.Payment.create(params, { transaction }); + + p2 = p2.get({ plain: true }); + p2.account = a2.get({ plain: true }); + emit(recipient.username, "payment", p2); + emit(recipient.username, "account", p2.account); + + l("received internal", recipient.username, amount); + notify(recipient, `Received ${amount} ${a2.ticker} sats`); + callWebhook(invoice, p2); + + } + + return payment; + }); +}; diff --git a/package.json b/package.json index 518c2c1a..4e7030fa 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "version": "0.0.1", "private": true, "scripts": { - "start": "NODE_OPTIONS=\"--es-module-specifier-resolution=node --experimental-modules --loader ./loader.js --openssl-legacy-provider\" node index.js", - "dev": "NODE_OPTIONS=\"--es-module-specifier-resolution=node --experimental-modules --loader ./loader.js --openssl-legacy-provider\" nodemon index.js" + "start": "node index.js", + "dev": "nodemon index.js" }, "dependencies": { "@fastify/multipart": "^7.1.2", @@ -9,6 +9,7 @@ services: - '3119:3119' environment: NODE_ENV: production + NODE_OPTIONS: "--es-module-specifier-resolution=node --experimental-modules --loader ./loader.js --openssl-legacy-provider" volumes: - ./:/app - ./logs:/logs diff --git a/routes/liquid/receive.js b/routes/liquid/receive.js index 9c2b490b..480677de 100644 --- a/routes/liquid/receive.js +++ b/routes/liquid/receive.js @@ -11,18 +11,16 @@ import zmq from "zeromq/v5-compat"; import { Op } from "@sequelize/core"; import { fromBase58 } from "bip32"; import bitcoin from "bitcoinjs-lib"; -// import liquidJs from "liquidjs-lib"; +import liquidJs from "liquidjs-lib"; import { l, err, warn } from "$lib/logging"; import lq from "$lib/liquid"; -// const { Block, networks, Transaction } = liquidJs; +const { Block, networks, Transaction } = liquidJs; -/* const network = networks[ config.liquid.network === "mainnet" ? "liquid" : config.liquid.network ]; - */ import { computeConversionFee } from "./conversionFee"; diff --git a/routes/lnurl.js b/routes/lnurl.js index e5f426a6..4335d566 100644 --- a/routes/lnurl.js +++ b/routes/lnurl.js @@ -15,7 +15,6 @@ import jwt from "jsonwebtoken"; import qs from "query-string"; import bolt11 from "bolt11"; - import { createInvoice, decodePaymentRequest, getPayments } from "lightning"; import { @@ -24,6 +23,7 @@ import { } from "./lightning/conversionFee.js"; import send from "$lib/send"; +import sendInternal from "$lib/sendInternal"; const logins = persist("data/logins.json"); const recipients = persist("data/recipients.json"); @@ -197,19 +197,14 @@ app.post("/pay", auth, async (req, res, next) => { lnurlPayments[secret] = user.id; if (recipients[secret]) { - url = `https://coinos.io/api/send`; - const { data } = await axios.post( - url, + let data = await sendInternal( { amount, memo: comment, username: recipients[secret].username }, - { - headers: { - authorization: req.headers.authorization - } - } + req.hostname, + user ); res.send(data); diff --git a/routes/send.js b/routes/send.js index 5d46f4de..360ad5b2 100644 --- a/routes/send.js +++ b/routes/send.js @@ -1,223 +1,11 @@ -import config from "$config"; -import db from "$db"; -import { emit } from "$lib/sockets"; -import store from "$lib/store"; -import { callWebhook } from "$lib/webhooks"; -import axios from "axios"; -import { v4 } from "uuid"; -import { notify } from "$lib/notifications"; -import { sendLiquid } from "$routes/liquid/send"; -import { l, err } from "$lib/logging"; +import sendInternal from "$lib/sendInternal"; export default async (req, res, next) => { - let { - amount, - address, - payreq, - unconfidential, - asset, - memo, - username - } = req.body; - let { user } = req; - amount = parseInt(amount); - - if (!asset) asset = config.liquid.btcasset; - - if (!amount || amount < 0) - return res.code(500).send("Amount must be greater than zero"); - try { - await db.transaction(async transaction => { - let account; - if (user.account.asset === asset && !user.account.pubkey) { - account = await db.Account.findOne({ - where: { - id: user.account.id, - pubkey: null - }, - lock: transaction.LOCK.UPDATE, - transaction - }); - } else { - account = await db.Account.findOne({ - where: { - user_id: user.id, - asset, - pubkey: null - }, - lock: transaction.LOCK.UPDATE, - transaction - }); - } - - if (!account) throw new Error("Account not found"); - - if (account.balance < amount) { - throw new Error("Insufficient funds"); - } - - let fee = 0; - - await account.decrement({ balance: amount }, { transaction }); - await account.reload({ transaction }); - - let params = { - amount: -amount, - account_id: account.id, - memo, - user_id: user.id, - rate: store.rates[user.currency], - currency: user.currency, - confirmed: true, - hash: `#${v4().substr(0, 6)} ${ - username ? `Payment to ${username}` : "Internal Transfer" - }`, - network: "COINOS" - }; - - if (!username) { - l("creating redeemable payment"); - params.redeemcode = v4(); - params.hash = `${req.hostname}/redeem/${params.redeemcode}`; - } - - let payment = await db.Payment.create(params, { transaction }); - - payment = payment.get({ plain: true }); - payment.account = account.get({ plain: true }); - - l("sent internal", user.username, -payment.amount); - - emit(user.username, "payment", payment); - emit(user.username, "account", account); - - if (username) { - let recipient = await db.User.findOne( - { - where: { username }, - include: { - model: db.Account, - as: "account" - } - }, - { transaction } - ); - - params = { - where: { user_id: recipient.id }, - order: [["id", "DESC"]] - }; - if (address) params.where.address = address; - else if (payreq) params.where.text = payreq; - let invoice = (address || payreq) && (await db.Invoice.findOne(params)); - - let a2; - let acc = { - user_id: recipient.id, - asset, - pubkey: null - }; - - if (recipient.account.asset === asset && !recipient.account.pubkey) - a2 = recipient.account; - else { - a2 = await db.Account.findOne({ - where: acc, - lock: transaction.LOCK.UPDATE, - transaction - }); - } - - if (a2) { - await a2.increment({ balance: amount }, { transaction }); - await a2.reload({ transaction }); - } else { - let name = asset.substr(0, 6); - let domain; - let ticker = asset.substr(0, 3).toUpperCase(); - let precision = 8; - - if (store.assets[asset]) { - ({ domain, ticker, precision, name } = store.assets[asset]); - } else { - const existing = await db.Account.findOne({ - where: { - asset - }, - order: [["id", "ASC"]], - limit: 1, - lock: transaction.LOCK.UPDATE, - transaction - }); - - if (existing) { - ({ domain, ticker, precision, name } = existing); - } - } - - acc = { ...acc, ...{ domain, ticker, precision, name } }; - acc.balance = amount; - acc.pending = 0; - acc.network = "liquid"; - a2 = await db.Account.create(acc, { transaction }); - } - - let spread = user.username === "coinos" ? 1.02 : 1; - amount = Math.round(amount / spread); - - params = { - amount, - account_id: a2.id, - user_id: recipient.id, - rate: store.rates[recipient.currency] * spread, - currency: recipient.currency, - confirmed: true, - hash: `#${v4().substr(0, 6)} Payment from ${user.username}`, - memo, - network: "COINOS", - received: true - }; - - if (invoice) { - params.invoice_id = invoice.id; - - let c = store.convert[invoice.text]; - if (c) { - l( - "internal payment detected for conversion", - invoice.text, - c.address, - recipient.username - ); - - recipient.account = a2; - - sendLiquid({ - address: c.address, - amount: amount - 100, - user: recipient, - limit: amount - }).catch(console.log); - } - } - - let p2 = await db.Payment.create(params, { transaction }); - - p2 = p2.get({ plain: true }); - p2.account = a2.get({ plain: true }); - emit(recipient.username, "payment", p2); - emit(recipient.username, "account", p2.account); - - l("received internal", recipient.username, amount); - notify(recipient, `Received ${amount} ${a2.ticker} sats`); - callWebhook(invoice, p2); - } - res.send(payment); - }); + let payment = await sendInternal(req.body, req.hostname, req.user); + res.send(payment); } catch (e) { - console.log(e); - err("problem sending internal payment", user.username, e.message); + err("problem sending internal payment", e.message); return res.code(500).send(e.message); } }; diff --git a/sampleconfig/lnurl.js b/sampleconfig/lnurl.js new file mode 100644 index 00000000..6f358983 --- /dev/null +++ b/sampleconfig/lnurl.js @@ -0,0 +1,46 @@ +import fs from "fs"; + +export default { + host: "app", + port: 3118, + protocol: "http", + url: "https://desk.bobcat-liberty.ts.net", + endpoint: "/lnurl", + listen: true, + auth: { + apiKeys: [ + { + id: "50bdeb634d", + key: "ac9bdd15a983072311c74c13877d122f16a27a8f40cb4dfa5014eece4da5cda2" + } + ] + }, + lightning: { + backend: "lnd", + config: { + hostname: "lnd:9735", + cert: { + data: fs.readFileSync( + "/app/config/lnd/tls.cert", + "utf8" + ) + }, + macaroon: { + data: + "0201036c6e6402f801030a100a1edc729d77423ec23fa75b85b781391201301a160a0761646472657373120472656164120577726974651a130a04696e666f120472656164120577726974651a170a08696e766f69636573120472656164120577726974651a210a086d616361726f6f6e120867656e6572617465120472656164120577726974651a160a076d657373616765120472656164120577726974651a170a086f6666636861696e120472656164120577726974651a160a076f6e636861696e120472656164120577726974651a140a057065657273120472656164120577726974651a180a067369676e6572120867656e657261746512047265616400000620aea455def2702fb1ec9838f1300255147ed20e825a539a1ee2e99e7049b04c11" + } + } + }, + store: { + backend: 'knex', + config: { + client: 'mysql', + connection: { + host: 'maria', + user: 'root', + password: 'password', + database: 'coinos', + }, + }, + }, +}; |
