diff options
| author | Adam Soltys <[email protected]> | 2023-01-08 21:05:34 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2023-01-08 21:05:34 -0800 |
| commit | dce07aa93b3c2f47aca0c24cea6adde9ec37f1a7 (patch) | |
| tree | 6c6b8930ebe9c2cc5b8d1f759e61260f509fdd7a /routes | |
| parent | f7b844883506a8072213245b9b8a2b988694e4f5 (diff) | |
| download | coinos-server-dce07aa93b3c2f47aca0c24cea6adde9ec37f1a7.tar.gz coinos-server-dce07aa93b3c2f47aca0c24cea6adde9ec37f1a7.zip | |
tech debt jubilee
Diffstat (limited to 'routes')
30 files changed, 174 insertions, 4329 deletions
diff --git a/routes/admin.js b/routes/admin.js deleted file mode 100644 index 3053d813..00000000 --- a/routes/admin.js +++ /dev/null @@ -1,566 +0,0 @@ -import express from "express"; -var router = express.Router(); - -import * as parseInput from "../scripts/apiParse"; -import Sequelize from "@sequelize/core"; -const Op = Sequelize.Op; - -/** - * @api {get} /users Retrieve user list - * @apiName getUsers - * @apiGroup Admin - * - * @apiPermission admin - * - * @apiSuccess {Object} users List of users - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "users" : [ - * { "username": 'John Doe', "email": '[email protected]', ...}, - * { "username": 'Jane Doe', "email": '[email protected]', ...} - * ] - * } - */ -router.get( - "/users", - // auth, - async (req, res) => { - var { search, starts_with, contains } = req.query; - var users = knex - .select( - "username", - "email", - "phone", - "verified", - knex.raw("LEFT(createdAt,10) as created_at") - ) - .from("users"); - - var timeCondition = parseInput.addTimeSearch(req.query, "users.createdAt"); - var userCondition = parseInput.addUserSearch(req.query); - - if (timeCondition) users = users.whereRaw(timeCondition); - if (userCondition) users = users.whereRaw(userCondition); - - // Alternative Sequelize query syntax - // await db.User.findAll({ - // attributes: ['username', 'email', 'phone', 'verified', 'createdAt'] - // }) - - const found = await users; - return res.send({ users: found }); - } -); - -/** - * @api {get} /referrals Retrieve list of referral tokens - * @apiName referrals - * @apiGroup Admin - * - * @apiPermission admin - * - * @apiSuccess returns list of referral tokens - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "referrals" : [ - * { "token": '***', "status": 'pending', sponsor: 'Adam', user: 'newUsername1', expiry: null }, - * { "token": '***', "status": 'pending', sponsor: 'Adam', user: 'newUsername2', expiry: null }, - * ] - * } - */ -router.get("/referrals", auth, async (req, res) => { - var referrals = knex - .select( - "token", - "users.username as user", - "sponsor.username as sponsor", - knex.raw("LEFT(created_at, 10) as created_at"), - "referrals.status" - ) - .from("referrals") - .leftJoin("users", "users.id", "referrals.user_id") - .leftJoin("users as sponsor", "sponsor.id", "referrals.sponsor_id"); - - // Alternative Sequelize query syntax (returns sub-tables as hash) - // var referrals = await db.Referral.findAll({ - // include: [ - // {model: db.User, as: 'user', attributes: [['username', 'user']]}, - // {model: db.User, as: 'sponsor', attributes: [['username', 'sponsor']]} - // ] - // }) - - var timeCondition = parseInput.addTimeSearch( - req.query, - "referrals.updated_at" - ); - var userCondition = parseInput.addUserSearch(req.query, "sponsor"); - - if (userCondition) { - referrals = referrals.whereRaw(userCondition); - } - if (timeCondition) { - referrals = referrals.whereRaw(timeCondition); - } - - var found = await referrals; - return res.send({ referrals: found }); -}); - -/** - * @api {get} /waiting_list Retrieve waiting_list - * @apiName waiting_list - * @apiGroup Admin - * - * @apiPermission admin - * - * @apiSuccess returns list of referral tokens - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "waiting_list" : [ - * { email: "[email protected]", phone: '604 123-4567 }, - * { email: "[email protected]", phone: '604 246-8910 }, - * ] - * } - */ -router.get( - "/waiting_list", - // auth, - async (req, res) => { - var { search, starts_with, contains } = req.query; - - var queue = knex - .select( - "waiting_list.email", - "waiting_list.phone", - "waiting_list.created_at as requested", - "users.id as current_user_id" - ) - .from("waiting_list") - .leftJoin("users", "users.email", "waiting_list.email") - .where("waiting_list.id", ">", 0); - - var timeCondition = parseInput.addTimeSearch( - req.query, - "waiting_list.updated_at" - ); - var userCondition = parseInput.addUserSearch(req.query); - - if (timeCondition) queue = queue.whereRaw(timeCondition); - if (userCondition) queue = queue.whereRaw(userCondition); - - const found = await queue; - - return res.send({ queue: found }); - } -); - -/** - * @api {get} /accounts Retrieve list of accounts - * @apiName accounts - * @apiGroup Admin - * - * @apiPermission admin - * - * @apiSuccess returns list of accounts - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "accounts" : [ - * { "user": 'Adam', "account_id": '123', balance: 500 ... }, - * ... - * ] - * } - */ -router.get("/accounts", auth, async (req, res) => { - var { nonZero, search, starts_with, contains } = req.query; - - var accounts = knex - .select( - "username", - "accounts.id as account_id", - "ticker", - "balance", - knex.raw("LEFT(accounts.createdAt, 10) as created"), - knex.raw("LEFT(accounts.updatedAt, 10) as updated") - ) - .from("accounts") - .leftJoin("users", "users.id", "accounts.user_id"); - - if (nonZero) { - accounts = accounts.where("balance", ">", 0); - } - - var timeCondition = parseInput.addTimeSearch(req.query, "accounts.updatedAt"); - var userCondition = parseInput.addUserSearch(req.query); - - if (timeCondition) accounts = accounts.whereRaw(timeCondition); - if (userCondition) accounts = accounts.whereRaw(userCondition); - - const found = await accounts; - - return res.send({ accounts: found }); -}); - -/** - * @api {get} /transactions Retrieve summary of user transactions - * - * @apiName user_transactions - * @apiGroup Admin - * - * @apiPermission admin - * - * @apiSuccess returns list of invoices, payments, orders for users - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "transactions" : [ - * { "username": 'Adam', "email": '[email protected]', invoices: 1, orders: 0, payments: 2 }, - ... - * ] - * } - */ -router.get( - "/transactions", - // auth, - async (req, res) => { - var { type, since, search, starts_with, contains } = req.query; - - var transactions = knex - .select( - "users.username", - "users.email" - // knex.raw('LEFT(created_at, 10) as created') - // knex.raw('LEFT(updatedAt, 10) as updated') - // knex.raw('COUNT(invoices.id) as invoices'), - // knex.raw('COUNT(payments.id) as payments'), - // knex.raw('COUNT(orders.id) as orders') - ) - .from("users"); - - const types = ["deposits", "withdrawals", "orders", "payments", "invoices"]; - for (var i = 0; i < types.length; i++) { - var stamp = types[i].replace(/s$/, "") + "_date"; - - transactions = transactions - .select( - knex.raw("COUNT(DISTINCT " + types[i] + ".id) as " + types[i]), - knex.raw("LEFT(MAX(" + types[i] + ".updatedAt), 10) as " + stamp) - ) - .leftJoin(types[i], "users.id", types[i] + ".user_id"); - // .havingRaw(types[i] + ' > ?', [0]) - - if (since) { - transactions = transactions.havingRaw( - "(" + stamp + " >= ? OR " + stamp + " IS NULL)", - [since] - ); - } - } - - if (since) - transactions = transactions.havingRaw( - "invoices + payments + deposits + orders > ?", - [0] - ); - - var userCondition = parseInput.addUserSearch(req.query); - if (userCondition) transactions = transactions.whereRaw(userCondition); - - transactions = transactions.groupBy("users.id"); - - const found = await transactions; - - return res.send({ transactions: found }); - } -); - -/** - * @api {get} /orders Retrieve summary of orders - * - * @apiName orders - * @apiGroup Admin - * - * @apiPermission admin - * - * @apiSuccess returns list of orders - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "orders" : [ - * { "seller": 'Adam', "buyer": 'bob' ... }, - ... - * ] - * } - */ -router.get("/orders", auth, async (req, res) => { - var { type, since, search, starts_with, contains } = req.query; - - var orders = knex - .select( - "u1.username as seller", - "u2.username as buyer", - "v1 as sell_amount", - "v2 as buy_amount", - "rate", - "a1.name as from", - "a2.name as to", - knex.raw("Left(completedAt,16) as completed") - ) - .from("orders") - .leftJoin("accounts as a1", "a1.id", "orders.a1_id") - .leftJoin("accounts as a2", "a2.id", "orders.a2_id") - .leftJoin("users as u1", "a1.user_id", "u1.id") - .leftJoin("users as u2", "a2.user_id", "u2.id"); - - var timeCondition = parseInput.addTimeSearch(req.query, "orders.updatedAt"); - if (timeCondition) orders = orders.whereRaw(timeCondition); - - var userCondition1 = parseInput.addUserSearch(req.query, "u1"); - var userCondition2 = parseInput.addUserSearch(req.query, "u2"); - - if (userCondition1) - orders = orders.where(function() { - this.whereRaw(userCondition1); - this.orWhere(knex.raw(userCondition2)); - }); - - var found = await orders; - return res.send({ orders: found }); -}); - -/** - * @api {get} /invoices Retrieve summary of invoices - * - * @apiName invoices - * @apiGroup Admin - * - * @apiPermission admin - * - * @apiSuccess returns list of invoices - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "invoices" : [ - * { "seller": 'Adam', "buyer": 'bob' ... }, - ... - * ] - * } - */ -router.get("/invoices", auth, async (req, res) => { - var { type, since, search, starts_with, contains } = req.query; - - var invoices = knex - .select( - "username", - "users.email", - "invoices.currency", - "invoices.amount", - "accounts.network", - knex.raw("Left(invoices.updatedAt,16) as updated") - ) - .from("invoices") - .leftJoin("accounts", "invoices.account_id", "accounts.id") - .leftJoin("users", "accounts.user_id", "users.id"); - - var timeCondition = parseInput.addTimeSearch(req.query, "invoices.updatedAt"); - var userCondition = parseInput.addUserSearch(req.query); - - if (timeCondition) invoices = invoices.whereRaw(timeCondition); - if (userCondition) invoices = invoices.whereRaw(userCondition); - - var found = await invoices; - return res.send({ invoices: found }); -}); - -/** - * @api {get} /deposits Retrieve summary of deposits - * - * @apiName deposits - * @apiGroup Admin - * - * @apiPermission admin - * - * @apiSuccess returns list of deposits - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "deposits" : [ - * { "seller": 'Adam', "buyer": 'bob' ... }, - ... - * ] - * } - */ -router.get("/deposits", auth, async (req, res) => { - var { type, since, search, starts_with, contains } = req.query; - - var deposits = knex - .select( - "username", - "users.email", - "amount", - "credited", - knex.raw("Left(deposits.updatedAt,16) as deposited") - ) - .from("deposits") - .leftJoin("users", "deposits.user_id", "users.id"); - - var timeCondition = parseInput.addTimeSearch(req.query, "deposits.updatedAt"); - var userCondition = parseInput.addUserSearch(req.query); - - if (timeCondition) deposits = deposits.whereRaw(timeCondition); - if (userCondition) deposits = deposits.whereRaw(userCondition); - - var found = await deposits; - return res.send({ deposits: found }); -}); - -/** - * @api {get} /withdrawals Retrieve summary of withdrawals - * - * @apiName withdrawals - * @apiGroup Admin - * - * @apiPermission admin - * - * @apiSuccess returns list of withdrawals - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "withdrawals" : [ - * { "seller": 'Adam', "buyer": 'bob' ... }, - ... - * ] - * } - */ -router.get("/withdrawals", auth, async (req, res) => { - var { type, since, search, starts_with, contains } = req.query; - - var withdrawals = knex - .select( - "username", - "users.email", - "amount", - "notes", - knex.raw("Left(withdrawals.updatedAt,16) as withdrawn") - ) - .from("withdrawals") - .leftJoin("users", "withdrawals.user_id", "users.id"); - - var timeCondition = parseInput.addTimeSearch( - req.query, - "withdrawals.updatedAt" - ); - var userCondition = parseInput.addUserSearch(req.query); - - if (timeCondition) withdrawals = withdrawals.whereRaw(timeCondition); - if (userCondition) withdrawals = withdrawals.whereRaw(userCondition); - - var found = await withdrawals; - return res.send({ withdrawals: found }); -}); - -/** - * @api {get} /payments Retrieve summary of payments - * - * @apiName payments - * @apiGroup Admin - * - * @apiPermission admin - * - * @apiSuccess returns list of payments - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "payments" : [ - * { "seller": 'Adam', "buyer": 'bob' ... }, - ... - * ] - * } -**/ -router.get("/payments", auth, async (req, res) => { - var { type, since, search, starts_with, contains } = req.query; - - var payments = knex - .select( - "username", - "users.email", - "amount", - "payments.network", - knex.raw("LEFT(hash,20) as hash"), - knex.raw("Left(payments.updatedAt,16) as deposited") - ) - .from("payments") - .leftJoin("accounts", "payments.account_id", "accounts.id") - .leftJoin("users", "accounts.user_id", "users.id"); - - var timeCondition = parseInput.addTimeSearch(req.query, "payments.updatedAt"); - var userCondition = parseInput.addUserSearch(req.query); - - if (timeCondition) payments = payments.whereRaw(timeCondition); - if (userCondition) payments = payments.whereRaw(userCondition); - - var found = await payments; - return res.send({ payments: found }); -}); - -/** - * @api {get} /user_kyc Retrieve user kyc details - * - * @apiName user_kyc - * @apiGroup Admin - * - * @apiPermission admin - * - * @apiSuccess returns list of kyc details for each user - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "users" : [ - * { "username": 'Adam', "email": '[email protected]', ... kyc_data... }, - ... - * ] - * } - */ -router.get("/kyc_required", auth, async (req, res) => { - var { since, search, starts_with, contains } = req.query; - - var threshold = "0.2"; - - var kyc = knex - .select( - knex.raw("CONCAT( FORMAT(Max(amount)/1000000, 1), ' M') as max"), - "username", - "users.email", - "users.verified as kyc_verified", - // 'payments.currency', - // 'payments.network', - // knex.raw('LEFT(hash,20) as hash'), - // knex.raw('Left(payments.updatedAt,16) as transferred') - // knex.raw( parseInput.readableDate('Max(payments.updatedAt)', 'last_changed') ) - knex.raw( - "DATE_FORMAT(Max(payments.updatedAt), ?) AS last_changed", - "%M %d, %Y" - ) - ) - .from("payments") - .leftJoin("accounts", "payments.account_id", "accounts.id") - .leftJoin("users", "payments.user_id", "users.id") // should really point to accounts - .groupBy("users.id"); - // .groupBy('payments.currency') - // .groupBy('payments.network') - - var timeCondition = parseInput.addTimeSearch(req.query, "payments.updatedAt"); - var userCondition = parseInput.addUserSearch(req.query); - - if (timeCondition) kyc = kyc.whereRaw(timeCondition); - if (userCondition) kyc = kyc.whereRaw(userCondition); - - var found = await kyc.having("max", ">=", threshold); - - return res.send({ found: found }); -}); - -export default router; diff --git a/routes/assets.js b/routes/assets.js deleted file mode 100644 index ed0d3036..00000000 --- a/routes/assets.js +++ /dev/null @@ -1,536 +0,0 @@ -import { emit } from "$lib/sockets"; -import store from "$lib/store"; -import db from "$db"; -import config from "$config"; -import app from "$app"; -import { auth } from "$lib/passport"; -import axios from "axios"; -import crypto from "crypto"; -import fs from "fs"; -import { err, l } from "$lib/logging"; -import lq from "$lib/liquid"; -import { SATS } from "$lib/utils"; - -let fetchAssets; -(fetchAssets = async () => { - try { - const { data } = await axios.get("https://assets.blockstream.info/"); - - store.assets = data; - } catch (e) { - var liquid_assets = JSON.parse(fs.readFileSync("/app/assets.json")); - if (liquid_assets) { - store.assets = liquid_assets; - console.debug("using static assets..." + e.message); - } else { - err("error fetching assets", e.message); - res.code(500).send("error fetching assets"); - } - } - - setTimeout(fetchAssets, 7200000); -})(); - -app.get("/assets", async (req, res) => { - if (store.assets) { - const accounts = await db.Account.findAll({ - // group: ["asset"] - }); - Object.keys(store.assets).map(a => { - store.assets[a].registered = true; - if (!store.assets[a].asset) - store.assets[a].asset = store.assets[a].asset_id; - if ( - (store.assets[a].ticker === "BTC" && - store.assets[a].asset !== config.liquid.btcasset) || - (store.assets[a].ticker === "EUR" && - store.assets[a].asset !== config.liquid.eurasset) || - (store.assets[a].ticker === "CAD" && - store.assets[a].asset !== config.liquid.cadasset) || - (store.assets[a].ticker === "USDt" && - store.assets[a].asset !== config.liquid.usdtasset) - ) - delete store.assets[a]; - }); - accounts.map(({ asset, name, domain, ticker, precision }) => { - if (!store.assets[asset]) - store.assets[asset] = { - asset, - name, - domain, - ticker, - precision - }; - }); - - res.send(store.assets); - } else { - console.log("error getting blockstream assets"); - res.code(500).send("Problem fetching blockstream asset registry data"); - } -}); - -app.post("/assets", auth, async (req, res) => { - try { - const sha256 = crypto.createHash("sha256"); - const { id: user_id } = req.user; - const blind = false; - - if (!user_id) throw new Error("unauthorized"); - - const { - address, - domain, - name, - asset_amount, - filename, - token_amount, - precision, - pubkey, - ticker - } = req.body; - - const asset_address = address - ? address - : await lq.getNewAddress("", "legacy"); - - const token_address = - token_amount && (await lq.getNewAddress("", "legacy")); - - const issuer_pubkey = pubkey - ? pubkey - : (await lq.getAddressInfo(asset_address)).pubkey; - const version = 0; - - let contract = { - entity: { domain }, - issuer_pubkey, - name, - precision, - version - }; - - if (filename) contract.file = filename; - if (ticker) contract.ticker = ticker; - - contract = Object.keys(contract) - .sort() - .reduce((r, k) => ((r[k] = contract[k]), r), {}); - - l( - "attempting issuance", - req.user.username, - JSON.stringify(contract), - "address", - address, - "pubkey", - pubkey - ); - - sha256.update(JSON.stringify(contract)); - const hash = sha256.digest("hex"); - const contract_hash = hash - .match(/[a-f0-9]{2}/g) - .reverse() - .join(""); - const rawtx = await lq.createRawTransaction([], [{ data: "00" }]); - const funded = await lq.fundRawTransaction(rawtx, { feeRate: 0.000002 }); - const params = { - asset_amount, - asset_address, - blind, - contract_hash - }; - - params.asset_amount = asset_amount / (SATS / 10 ** precision); - - if (token_amount) { - params.token_amount = token_amount; - params.token_address = token_address; - params.token_amount = parseInt(params.token_amount); - } - - let ria; - try { - ria = await lq.rawIssueAsset(funded.hex, [params]); - } catch (e) { - l(asset_amount, token_amount, params); - throw new Error(e.message); - } - - const { asset, hex, token } = ria[0]; - const brt = await lq.blindRawTransaction(hex, true, [], false); - const srt = await lq.signRawTransactionWithWallet(brt); - const allowed = (await lq.testMempoolAccept([srt.hex]))[0].allowed; - if (!allowed) throw new Error("issuance rejected by mempool", srt.hex); - const txid = await lq.sendRawTransaction(srt.hex); - - await db.transaction(async transaction => { - let account = await db.Account.findOne({ - where: { - user_id, - asset: config.liquid.btcasset, - pubkey: null - }, - include: { - model: db.User, - as: "user" - }, - order: [["balance", "DESC"]], - lock: transaction.LOCK.UPDATE, - transaction - }); - - let { user } = account; - - if (Math.round(funded.fee * SATS) > account.balance) { - err( - "amount exceeds balance", - asset_amount, - funded.fee, - account.balance - ); - throw new Error(`Insufficient funds to pay fee of ${funded.fee} BTC`); - } - - await account.decrement( - { balance: Math.round(funded.fee * SATS) }, - { transaction } - ); - await account.reload({ transaction }); - emit(user.username, "account", account); - - let invoice; - let recipient = user; - if (address) { - invoice = await db.Invoice.findOne({ - where: { address }, - include: { - model: db.User, - as: "user" - } - }); - recipient = invoice.user; - } - - if (!invoice) { - invoice = await db.Invoice.create( - { - account_id: account.id, - user_id, - amount: Math.round(params.asset_amount * SATS), - address: asset_address, - currency: user.currency, - rate: store.rates[user.currency], - network: "liquid" - }, - { transaction } - ); - } - - account = await db.Account.create( - { - asset, - contract, - domain, - user_id: invoice.user_id, - ticker, - precision, - name, - balance: 0, - network: "liquid", - pending: Math.round(params.asset_amount * SATS) - }, - { transaction } - ); - - emit(recipient.username, "account", account); - l( - "issued asset", - user.username, - params.asset_amount, - ticker, - name, - account.id - ); - - const asset_payment = await db.Payment.create( - { - account_id: account.id, - user_id: invoice.user_id, - invoice_id: invoice.id, - hash: txid, - amount: Math.round(params.asset_amount * SATS), - received: true, - confirmed: false, - address: asset_address, - network: "liquid" - }, - { transaction } - ); - emit(recipient.username, "payment", asset_payment); - - if (invoice.user_id !== user.id) { - const asset_send = await db.Payment.create( - { - account_id: account.id, - user_id, - hash: txid, - amount: -Math.round(params.asset_amount * SATS), - received: false, - confirmed: false, - address, - network: "liquid" - }, - { transaction } - ); - emit(recipient.username, "payment", asset_send); - } - - store.issuances[txid] = { - user_id, - asset, - asset_amount: params.asset_amount, - asset_payment_id: asset_payment.id - }; - - if (token_amount) { - invoice = null; - recipient = user; - - if (address) { - invoice = await db.Invoice.findOne({ - where: { address }, - include: { - model: db.User, - as: "user" - } - }); - recipient = invoice.user; - } - - if (!invoice) { - invoice = await db.Invoice.create( - { - account_id: account.id, - user_id, - amount: Math.round(params.token_amount * SATS), - address: token_address, - currency: user.currency, - rate: store.rates[user.currency], - network: "liquid" - }, - { transaction } - ); - } - - account = await db.Account.create( - { - asset: token, - user_id: invoice.user_id, - domain, - ticker: `${ticker}REISSUANCETOKEN`, - precision: 8, - name: `${name} Reissuance Token`, - balance: 0, - pending: address ? 0 : Math.round(token_amount * SATS) - }, - { transaction } - ); - emit(recipient.username, "account", account); - - const token_payment = await db.Payment.create( - { - account_id: account.id, - invoice_id: invoice.id, - user_id, - hash: txid, - amount: Math.round(token_amount * SATS), - received: true, - confirmed: false, - address: token_address, - network: "liquid" - }, - { transaction } - ); - emit(recipient.username, "payment", token_payment); - - if (invoice.user_id !== user.id) { - const token_send = await db.Payment.create( - { - account_id: account.id, - invoice_id: invoice.id, - user_id, - hash: txid, - amount: -Math.round(token_amount * SATS), - received: false, - confirmed: false, - address, - network: "liquid" - }, - { transaction } - ); - emit(recipient.username, "payment", token_send); - } - - store.issuances[txid].token = token; - store.issuances[txid].token_amount = token_amount; - store.issuances[txid].token_payment_id = token_payment.id; - } - }); - - res.send(store.issuances[txid] ? store.issuances[txid] : { asset }); - } catch (e) { - err("asset issuance failed", e.message, e.stack); - res.code(500).send(e.message); - } -}); - -app.post("/assets/register", auth, async (req, res) => { - const { asset } = req.body; - const account = await db.Account.findOne({ - where: { - user_id: req.user.id, - asset - } - }); - - l("registering", asset, account.contract); - - try { - const { data: result } = await axios.post( - "https://assets.blockstream.info/", - { - asset_id: asset, - contract: account.contract - } - ); - l("register asset result", req.user.username, result); - res.send(result); - } catch (e) { - err("asset registration failed", e.message); - res.code(500).send(e.message); - } -}); - -app.post("/loadFaucet", auth, async (req, res) => { - return res.code(500).send("Faucet feature temporarily disabled"); - - const { user } = req; - let { asset, amount } = req.body; - amount = parseInt(amount); - - try { - if (amount <= 0) throw new Error("Amount to load cannot be negative"); - - await db.transaction(async transaction => { - let account = await getAccount(config.liquid.btcasset, user, transaction); - - if (account.asset !== config.liquid.btcasset) - throw new Error( - "Faucet has to be funded with bitcoin. Try sending from another wallet." - ); - if (account.balance < amount) throw new Error("Insufficient funds"); - - let fee = 0; - - await account.decrement({ balance: amount }, { transaction }); - await account.reload({ transaction }); - - let a2; - let acc = { - user_id: null, - asset - }; - - 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; - - const assets = assets; - - if (assets[asset]) { - ({ domain, ticker, precision, name } = 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 params = { - amount: -amount, - account_id: account.id, - user_id: user.id, - rate: store.rates[user.currency], - currency: user.currency, - confirmed: true, - hash: `Loaded Faucet - ${a2.ticker}`, - network: "COINOS" - }; - - 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); - emit(user.username, "user", user); - - l("loaded faucet", asset, amount); - res.send({}); - }); - } catch (e) { - err("problem loading faucet", user.username, e.message); - return res.code(500).send(e.message); - } -}); - -app.get("/faucet", auth, async (req, res) => { - let { asset } = req.query; - let faucet = await db.Account.findOne({ - where: { - asset, - user_id: null - } - }); - - if (!faucet) - faucet = { - asset, - balance: 0, - ticker: "Unknown" - }; - - res.send(faucet); -}); diff --git a/routes/bitcoin/broadcast.js b/routes/bitcoin/broadcast.js deleted file mode 100644 index 85c8cf66..00000000 --- a/routes/bitcoin/broadcast.js +++ /dev/null @@ -1,56 +0,0 @@ -import { emit } from "$lib/sockets"; -import store from "$lib/store"; -import { requirePin } from "$lib/utils"; - -export default async (req, res) => { - try { - await requirePin(req); - - const { user } = req; - let hash = await bc.sendRawTransaction(req.body.tx); - - if (user) { - await db.transaction(async transaction => { - let account = await db.Account.findOne({ - where: { - id: user.account_id - }, - lock: transaction.LOCK.UPDATE, - transaction - }); - let payment = await db.Payment.create( - { - ...req.body.payment, - hash, - amount: -req.body.payment.amount, - tip: 0, - account_id: account.id, - user_id: user.id, - rate: store.rates[user.currency], - currency: user.currency, - confirmed: true, - received: false, - network: "bitcoin" - }, - { transaction } - ); - - await account.increment( - { balance: payment.amount - payment.fee }, - { transaction } - ); - await account.reload({ transaction }); - - payment = payment.get({ plain: true }); - payment.account = account.get({ plain: true }); - emit(user.username, "payment", payment); - emit(user.username, "account", payment.account); - res.send(payment); - }); - } else { - res.send(result); - } - } catch (e) { - res.code(500).send(e.message); - } -}; diff --git a/routes/bitcoin/conversionFee.js b/routes/bitcoin/conversionFee.js deleted file mode 100644 index 7e305659..00000000 --- a/routes/bitcoin/conversionFee.js +++ /dev/null @@ -1,12 +0,0 @@ -// Code for the conversion fee -// This is in a separate file so that the same function can be used by ./send and ./receive - -const conversionFeeMultiplier = 0.001; -export const conversionFeeReceiver = "coinosfees"; // account that receives the fees - -/** - * Returns the fee you have to pay for withdrawing 'amount' bitcoin. - */ -export function computeConversionFee(amount) { - return Math.floor(amount * conversionFeeMultiplier); -} diff --git a/routes/bitcoin/fee.js b/routes/bitcoin/fee.js deleted file mode 100644 index 74c26a92..00000000 --- a/routes/bitcoin/fee.js +++ /dev/null @@ -1,73 +0,0 @@ -import config from "$config"; -import db from "$db"; -import bc from "$lib/bitcoin"; -import { err } from "$lib/logging"; -import { emit } from "$lib/sockets"; -import buildTx from "$lib/buildtx"; -import { SATS } from "$lib/utils"; - -export default async (req, res) => { - let { user } = req; - let { address, amount, feeRate, replaceable = true } = req.body; - let tx, fee; - - if (user.account.pubkey) { - try { - let psbt = await buildTx({ address, amount, feeRate, replaceable, user }); - return res.send(psbt); - } catch (e) { - return res.code(500).send(e.message); - } - } - - let invoice = await db.Invoice.findOne({ - where: { address }, - include: { - attributes: ["username"], - model: db.User, - as: "user" - } - }); - - if (invoice) { - let { ismine } = await bc.getAddressInfo(address); - if (ismine) { - emit(user.username, "to", invoice.user); - return res.send({}); - } - } - - try { - amount = parseInt(amount); - - let partial = await bc.createRawTransaction( - [], - { - [address]: (amount / SATS).toFixed(8) - }, - 0, - replaceable - ); - - let params = { - subtractFeeFromOutputs: amount === user.balance ? [0] : [], - replaceable - }; - - if (feeRate) params.feeRate = (feeRate / SATS).toFixed(8); - - tx = await bc.fundRawTransaction(partial, params); - - if (config.bitcoin.walletpass) - await bc.walletPassphrase(config.bitcoin.walletpass, 300); - - let signed = await bc.signRawTransactionWithWallet(tx.hex); - let decoded = await bc.decodeRawTransaction(signed.hex); - feeRate = Math.round((tx.fee * SATS * 1000) / decoded.vsize); - - res.send({ feeRate, tx }); - } catch (e) { - err("bitcoin fee estimation error", e); - return res.code(500).send(e.message); - } -}; diff --git a/routes/bitcoin/generate.js b/routes/bitcoin/generate.js deleted file mode 100644 index 6285492d..00000000 --- a/routes/bitcoin/generate.js +++ /dev/null @@ -1,5 +0,0 @@ -export default async (req, res) => { - let address = await bc.getNewAddress(); - await bc.generateToAddress(1, address); - res.send({}); -}; diff --git a/routes/bitcoin/index.js b/routes/bitcoin/index.js deleted file mode 100644 index 58b0d193..00000000 --- a/routes/bitcoin/index.js +++ /dev/null @@ -1,13 +0,0 @@ -import broadcast from "./broadcast"; -import generate from "./generate"; -import sweep from "./sweep"; -import fee from "./fee"; -import send from "./send"; - -export default { - broadcast, - generate, - sweep, - fee, - send -}; diff --git a/routes/bitcoin/receive.js b/routes/bitcoin/receive.js deleted file mode 100644 index 10193ceb..00000000 --- a/routes/bitcoin/receive.js +++ /dev/null @@ -1,281 +0,0 @@ -import config from "$config"; -import db from "$db"; -import bc from "$lib/bitcoin"; -import { emit } from "$lib/sockets"; -import store from "$lib/store"; -import { notify } from "$lib/notifications"; -import { callWebhook } from "$lib/webhooks"; -import reverse from "buffer-reverse"; -import zmq from "zeromq/v5-compat"; -import { Op } from "@sequelize/core"; -import { fromBase58 } from "bip32"; -import bitcoin from "bitcoinjs-lib"; -import { sendLiquid } from "$routes/liquid/send"; -import { computeConversionFee } from "./conversionFee"; -import { l, warn, err } from "$lib/logging"; - -const zmqRawBlock = zmq.socket("sub"); -zmqRawBlock.connect(config.bitcoin.zmqrawblock); -zmqRawBlock.subscribe("rawblock"); - -const zmqRawTx = zmq.socket("sub"); -zmqRawTx.connect(config.bitcoin.zmqrawtx); -zmqRawTx.subscribe("rawtx"); - -const network = - bitcoin.networks[ - config.bitcoin.network === "mainnet" ? "bitcoin" : config.bitcoin.network - ]; - -const queue = {}; -const seen = []; - -console.warn = () => {}; - -zmqRawTx.on("message", async (topic, message, sequence) => { - try { - const hex = message.toString("hex"); - let tx = bitcoin.Transaction.fromHex(message); - - let hash = reverse(tx.getHash()).toString("hex"); - - if (seen.includes(hash)) return; - seen.push(hash); - if (seen.length > 5000) seen.shift(); - - Promise.all( - tx.outs.map(async o => { - try { - const { value } = o; - - let address; - try { - address = bitcoin.address.fromOutputScript(o.script, network); - } catch (e) { - return; - } - - if ( - Object.keys(store.addresses).includes(address) && - !store.change.includes(address) - ) { - await db.transaction(async transaction => { - let user = await db.User.findOne({ - where: { - username: store.addresses[address] - }, - transaction - }); - - let invoice = await db.Invoice.findOne({ - where: { - address, - user_id: user.id, - network: "bitcoin" - }, - order: [["id", "DESC"]], - include: { - model: db.Account, - as: "account" - }, - transaction - }); - - if (!invoice) return; - - const currency = invoice ? invoice.currency : user.currency; - const rate = invoice ? invoice.rate : store.rates[user.currency]; - const tip = invoice ? invoice.tip : 0; - const memo = invoice ? invoice.memo : ""; - - let confirmed = false; - - let { account } = invoice; - - if (account.asset !== config.liquid.btcasset) { - account = await db.Account.findOne({ - where: { - user_id: user.id, - asset: config.liquid.btcasset, - pubkey: null - }, - transaction - }); - } - - await invoice.increment({ pending: value }, { transaction }); - await invoice.reload({ transaction }); - - await account.increment({ pending: value }, { transaction }); - await account.reload({ transaction }); - - if (config.bitcoin.walletpass) - await bc.walletPassphrase(config.bitcoin.walletpass, 300); - - let totalOutputs = tx.outs.reduce((a, b) => a + b.value, 0); - let totalInputs = 0; - for (let i = 0; i < tx.ins.length; i++) { - let { hash, index } = tx.ins[i]; - hash = reverse(hash).toString("hex"); - let hex = await bc.getRawTransaction(hash); - let inputTx = bitcoin.Transaction.fromHex(hex); - totalInputs += inputTx.outs[index].value; - } - let fee = totalInputs - totalOutputs; - - let payment = await db.Payment.create( - { - account_id: account.id, - user_id: user.id, - hash, - fee, - memo, - amount: value - tip, - currency, - rate, - received: true, - tip, - confirmed, - address, - network: "bitcoin", - invoice_id: invoice.id - }, - { transaction } - ); - - payment = payment.get({ plain: true }); - payment.account = account.get({ plain: true }); - payment.invoice = invoice.get({ plain: true }); - - emit(user.username, "account", account); - emit(user.username, "payment", payment); - l("bitcoin detected", user.username, value); - notify(user, `${value} SAT payment detected`); - callWebhook(invoice, payment); - }); - } - } catch (e) { - console.log("problem parsing bitcoin tx", e); - } - }) - ); - } catch (e) { - console.log("problem receiving bitcoin tx", e); - } -}); - -zmqRawBlock.on("message", async (topic, message, sequence) => { - try { - const payments = await db.Payment.findAll({ - where: { confirmed: false } - }); - - let block = bitcoin.Block.fromHex(message.toString("hex")); - block.transactions.map(tx => { - let hash = reverse(tx.getHash()).toString("hex"); - if (payments.find(p => p.hash === hash)) queue[hash] = 1; - }); - } catch (e) { - console.log(e); - } -}); - -setInterval(async () => { - try { - const arr = Object.keys(queue); - for (let i = 0; i < arr.length; i++) { - const hash = arr[i]; - - let account, address, invoice, user, total; - await db.transaction(async transaction => { - let payments = await db.Payment.findAll({ - where: { hash, confirmed: 0, received: 1 }, - include: [ - { - model: db.Account, - as: "account" - }, - { - model: db.Invoice, - as: "invoice" - }, - { - model: db.User, - as: "user" - } - ], - lock: transaction.LOCK.UPDATE, - transaction - }); - - for (let i = 0; i < payments.length; i++) { - let p = payments[i]; - if (p && p.address) address = p.address; - if (p && p.user) user = p.user; - - if (p && p.account) { - ({ account, invoice } = p); - total = p.amount + p.tip; - - p.confirmed = 1; - await account.save({ transaction }); - - await invoice.increment({ received: total }, { transaction }); - await invoice.decrement( - { pending: Math.min(invoice.pending, total) }, - { transaction } - ); - - await account.increment({ balance: total }, { transaction }); - await account.decrement( - { pending: Math.min(account.pending, total) }, - { transaction } - ); - // get the # of fee credits you would need to pay off this amount of bitcoin - await account.increment( - { btc_credits: computeConversionFee(total) }, - { transaction } - ); - await account.reload({ transaction }); - await invoice.reload({ transaction }); - await p.save({ transaction }); - - p = p.get({ plain: true }); - p.account = account.get({ plain: true }); - p.invoice = invoice.get({ plain: true }); - - emit(user.username, "account", account); - emit(user.username, "payment", p); - l("bitcoin confirmed", user.username, p.amount, p.tip); - notify(user, `${total} SAT payment confirmed`); - callWebhook(p.invoice, p); - - let c = store.convert[address]; - if (address && c) { - l( - "bitcoin detected to conversion address", - address, - c.address, - user.username - ); - user.account = account; - - sendLiquid({ - address: c.address, - amount: total - 100, - user, - limit: total - }); - } - } else { - warn("couldn't find bitcoin payment", hash); - } - } - - delete queue[hash]; - }); - } - } catch (e) { - err("problem processing queued bitcoin transaction", e.message, e.stack); - } -}, 1000); diff --git a/routes/bitcoin/send.js b/routes/bitcoin/send.js deleted file mode 100644 index 3e030413..00000000 --- a/routes/bitcoin/send.js +++ /dev/null @@ -1,192 +0,0 @@ -import bc from "$lib/bitcoin"; -import { emit } from "$lib/sockets"; -import db from "$db"; -import { toSats } from "$lib/utils"; -import store from "$lib/store"; -import config from "$config"; -import { l, warn, err } from "$lib/logging"; -import { requirePin } from "$lib/utils"; - -const btc = config.liquid.btcasset; -import { - computeConversionFee, - conversionFeeReceiver, -} from "./conversionFee.js"; - -export default async (req, res) => { - try { - await requirePin(req); - - let { user } = req; - let { address, memo, tx } = req.body; - let { hex } = tx; - - let fee = toSats(tx.fee); - if (fee < 0) throw new Error("fee cannot be negative"); - - const isChange = async ({ address }) => - (await bc.getAddressInfo(address)).ismine && - !Object.keys(store.addresses).includes(address); - - tx = await bc.decodeRawTransaction(hex); - - let total = 0; - let change = 0; - - for (let i = 0; i < tx.vout.length; i++) { - let o = tx.vout[i]; - total += toSats(o.value); - - if (await isChange(o.scriptPubKey)) { - change += toSats(o.value); - } - } - - total = total - change + fee; - let amount = total - fee; - - // get conversion fee - // 'total' refers to the total before the conversion fee - // (i.e. the total bitcoin that leaves this server) - let conversionFee = computeConversionFee(amount); - - // withdraw bitcoin - await db.transaction(async (transaction) => { - let { account } = user; - if (account.asset !== btc) { - account = await db.Account.findOne({ - where: { - user_id: user.id, - asset: btc, - pubkey: null, - }, - lock: transaction.LOCK.UPDATE, - transaction, - }); - } - - // use user's credits to reduce fee, if available - let conversionFeeDeduction = Math.min(account.btc_credits, conversionFee); - if (conversionFeeDeduction) { - await account.decrement( - { btc_credits: conversionFeeDeduction }, - { transaction } - ); - await account.reload({ transaction }); - conversionFee -= conversionFeeDeduction; - } - - l( - "attempting to send bitcoin", - user.username, - account.id, - address, - total, - conversionFee, - conversionFeeDeduction - ); - - if (total > account.balance) { - err("amount exceeds balance", amount, fee, account.balance); - throw new Error("low"); - } else if (total + conversionFee > account.balance) { - err( - "total (after conversion fee) exceeds balance", - amount, - fee, - account.balance - ); - throw new Error("low"); - } - - await account.decrement( - { balance: total + conversionFee }, - { transaction } - ); - await account.reload({ transaction }); - - let receiverAccount = await db.Account.findOne({ - where: { - "$user.username$": conversionFeeReceiver, - }, - include: [ - { - model: db.User, - as: "user", - }, - ], - lock: transaction.LOCK.UPDATE, - transaction, - }); - - let fee_payment; - let fee_payment_id = null; - if (conversionFee) { - await receiverAccount.increment( - { balance: conversionFee }, - { transaction } - ); - await receiverAccount.reload({ transaction }); - fee_payment = await db.Payment.create( - { - amount: conversionFee, - fee: 0, - memo: "Bitcoin conversion fee", - account_id: receiverAccount.id, - user_id: receiverAccount.user_id, - rate: store.rates[receiverAccount.user.currency], - currency: receiverAccount.user.currency, - confirmed: true, - received: true, - network: "COINOS", - }, - { transaction } - ); - fee_payment_id = fee_payment.id; - } - - // record the external bitcoin transaction - let params = { - amount: -amount, - fee, - memo, - account_id: account.id, - user_id: user.id, - rate: store.rates[user.currency], - currency: user.currency, - address, - confirmed: true, - received: false, - network: "bitcoin", - fee_payment_id: fee_payment_id, - }; - - if (config.bitcoin.walletpass) - await bc.walletPassphrase(config.bitcoin.walletpass, 300); - - hex = (await bc.signRawTransactionWithWallet(hex)).hex; - params.hash = await bc.sendRawTransaction(hex); - - let payment = await db.Payment.create(params, { transaction }); - - payment = payment.get({ plain: true }); - payment.account = account.get({ plain: true }); - payment.fee_payment = fee_payment && fee_payment.get({ plain: true }); - - emit(user.username, "payment", payment); - res.send(payment); - - store.payments.push(params.hash); - l("sent bitcoin", user.username, total); - }); - } catch (e) { - if (e.message.includes("Insufficient")) - e.message = - "Our server has insufficient funds, try again later"; - - if (e.message === "low") e.message = "Insufficient funds in account" - - err("error sending bitcoin", e.message); - return res.code(500).send(e.message); - } -}; diff --git a/routes/bitcoin/sweep.js b/routes/bitcoin/sweep.js deleted file mode 100644 index 49fb1f04..00000000 --- a/routes/bitcoin/sweep.js +++ /dev/null @@ -1,87 +0,0 @@ -import config from "$config"; -import { prod } from "$lib/utils"; -import axios from "axios"; -import bitcoin from "bitcoinjs-lib"; -import coinselect from "coinselect"; -import split from "coinselect/split"; - -const api = prod ? "https://blockstream.info/api" : config.bitcoin.electrs; -const SATS = 100000000; -const network = prod - ? bitcoin.networks["bitcoin"] - : bitcoin.networks["regtest"]; - -export default async (req, res) => { - let { address: from, amount, feeRate, target } = req.body; - let utxos; - if (!feeRate) feeRate = (await bc.estimateSmartFee(6)).feerate * SATS; - feeRate = Math.round(feeRate / 1000); - l("sweeping", req.user.username, from, amount, feeRate); - - try { - let { - data: { - chain_stats: { funded_txo_sum: funded, spent_txo_sum: spent } - } - } = await axios.get(`${api}/address/${from}`); - let balance = funded - spent; - console.log("boom"); - - try { - console.log("fetching utxo"); - ({ data: utxos } = await axios.get(`${api}/address/${from}/utxo`)); - console.log("got utxos", utxos); - } catch (e) { - err("problem fetching utxos", e.message); - } - - let targets = [ - { - address: target, - value: amount - } - ]; - - let { inputs, outputs, fee } = coinselect(utxos, targets, feeRate); - - if (balance === amount) { - delete targets[0].value; - ({ inputs, outputs, fee } = split(utxos, targets, feeRate)); - l("split", inputs, outputs, fee); - } - - if (!inputs || !outputs) - return res - .code(500) - .send("Unable to construct sweep transaction, try a lower fee rate?"); - - let psbt = new bitcoin.Psbt({ network }); - let total = 0; - - outputs.map(({ address, value }) => { - total += value; - psbt = psbt.addOutput({ - address: address || from, - value - }); - }); - - for (let i = 0; i < inputs.length; i++) { - let { txid, vout } = inputs[i]; - let nonWitnessUtxo = Buffer.from(await bc.getRawTransaction(txid), "hex"); - psbt = psbt.addInput({ - hash: txid, - index: vout, - nonWitnessUtxo - }); - } - - feeRate = Math.round(feeRate * 1000); - psbt = psbt.toBase64(); - - if (balance !== amount) total = 0; - res.send({ feeRate, psbt, total }); - } catch (e) { - err("problem getting address stats", from, e.message); - } -}; diff --git a/routes/events.js b/routes/events.js index e52b8a7c..c105dc40 100644 --- a/routes/events.js +++ b/routes/events.js @@ -5,72 +5,3 @@ import { coinos, pool, q } from "$lib/nostr"; import store from "$lib/store"; import { nada, wait } from "$lib/utils"; -app.get("/event/:id", async (req, res) => { - try { - let event = JSON.parse(await redis.get(`ev:${req.params.id}`)); - let { pubkey } = event; - - event.user = JSON.parse(await redis.get(`user:${pubkey}`)) || { - username: pubkey.substr(0, 6), - pubkey, - anon: true, - follows: [], - followers: [] - }; - - res.send(event); - } catch (e) { - res.code(500).send("Problem fetching event"); - } -}); - -app.get("/nostr/:pubkey", async (req, res) => { - try { - let { pubkey } = req.params; - - let user = JSON.parse(await redis.get(`user:${pubkey}`)); - - if (!user) { - user = await db.User.findOne({ - where: { pubkey } - }); - } - - if (!user) - user = { - username: pubkey.substr(0, 6), - pubkey, - anon: true, - follows: [], - followers: [] - }; - - q( - `${pubkey}:notes`, - { - kinds: [1], - authors: [pubkey] - }, - { since: 0 } - ).catch(nada); - - await redis.set(`user:${pubkey}`, JSON.stringify(user)); - - let ids = await redis.sMembers(pubkey); - - let events = ids.length - ? (await redis.mGet(ids.map(k => "ev:" + k))).map(JSON.parse) - : []; - - res.send(events.map(e => ({ ...e, user }))); - } catch (e) { - console.log(e); - res.code(500).send("problem fetching user events"); - } -}); - -app.post("/nostr/send", async (req, res) => { - let { event } = req.body; - pool.send(["EVENT", event]); - res.send(event); -}); diff --git a/routes/funding.js b/routes/funding.js deleted file mode 100644 index e65a704e..00000000 --- a/routes/funding.js +++ /dev/null @@ -1,96 +0,0 @@ -import { emit } from "$lib/sockets"; -import app from "$app"; -import config from "$config"; -import mailgunFactory from "mailgun-js"; -const mailgun = mailgunFactory(config.mailgun); - -app.post("/id", auth, upload.single("id"), async (req, res) => { - const { user } = req; - if (user.verified === "proof") user.verified = "pending"; - else user.verified = "id"; - await user.save(); - emit(user.username, "user", user); - - let data = { - subject: "KYC Documents", - text: `ID uploaded for ${user.username}`, - from: "[email protected]", - to: "[email protected]" - }; - - mailgun.messages().send(data); - l("id uploaded", user.username); - - res.send({}); -}); - -app.post("/proof", auth, upload.single("proof"), async (req, res) => { - const { user } = req; - if (user.verified === "id") user.verified = "pending"; - else user.verified = "proof"; - await user.save(); - emit(user.username, "user", user); - l("id proof uploaded", user.username); - - res.send({}); -}); - -app.post("/funding", auth, async (req, res) => { - try { - let { id, amount, code } = req.body; - - if (!code) { - code = ""; - let d = "ABCDEFGHIJKLMNPQRSTUVWXYZ23456789"; - let l = d.length; - for (let i = 0; i < 8; i++) { - code += d.charAt(Math.floor(Math.random() * l)); - } - } - - let params = { - user_id: req.user.id, - amount, - code - }; - - let deposit; - if (id) { - if (!parseFloat(amount) || parseFloat(amount) < 0) - throw new Error("Invalid amount"); - deposit = await db.Deposit.findOne({ where: { id } }); - deposit.update(params); - await deposit.save(); - } else { - deposit = await db.Deposit.create(params); - } - - res.send(deposit); - } catch (e) { - err("funding error", e.message); - res.code(500).send("Funding request failed"); - } -}); - -app.post("/withdrawal", auth, async (req, res) => { - try { - const { account, amount, email, institution, transit, notes } = req.body; - if (!parseFloat(amount) || parseFloat(amount) < 0) - throw new Error("Invalid amount"); - - await db.Withdrawal.create({ - user_id: req.user.id, - account, - amount, - email, - institution, - transit, - notes - }); - - res.send({}); - } catch (e) { - err("withdrawaerr", e.message); - res.code(500).send("Withdrawal request failed"); - } -}); diff --git a/routes/invoices.js b/routes/invoices.js index 338c66e7..f3c95a21 100644 --- a/routes/invoices.js +++ b/routes/invoices.js @@ -1,13 +1,8 @@ import app from "$app"; -import db from "$db"; import store from "$lib/store"; import { optionalAuth } from "$lib/passport"; -import { Op } from "@sequelize/core"; -import axios from "axios"; import { err, l } from "$lib/logging"; -import bc from "$lib/bitcoin"; -import lq from "$lib/liquid"; -import { SATS, bip21, deriveAddress, derivePayRequest } from "$lib/utils"; +import { SATS, derivePayRequest } from "$lib/utils"; import { emit } from "$lib/sockets"; app.get("/invoice", async (req, res, next) => { diff --git a/routes/lightning/query.js b/routes/lightning/query.js index 1c217a08..c5456a6f 100644 --- a/routes/lightning/query.js +++ b/routes/lightning/query.js @@ -1,4 +1,3 @@ -import db from "$db"; import { emit } from "$lib/sockets"; export default async (req, res) => { diff --git a/routes/lightning/receive.js b/routes/lightning/receive.js index ef973acc..a778271b 100644 --- a/routes/lightning/receive.js +++ b/routes/lightning/receive.js @@ -1,11 +1,9 @@ -import db from "$db"; import store from "$lib/store"; import { emit } from "$lib/sockets"; import config from "$config"; import { notify } from "$lib/notifications"; import { callWebhook } from "$lib/webhooks"; import { computeConversionFee } from "./conversionFee"; -import { sendLiquid } from "$routes/liquid/send"; import ln from "$lib/ln"; import { l, err, warn } from "$lib/logging"; @@ -101,29 +99,6 @@ const handlePayment = async msg => { payment.tip ); }); - - let c = store.convert[msg.request]; - if (msg.request && c) { - l( - "lightning detected for conversion request", - msg.request, - c.address, - user.username - ); - - user.account = account; - - try { - sendLiquid({ - address: c.address, - amount: total - 100, - user, - limit: total - }); - } catch (e) { - err("problem sending liquid payment", e.message, e.stack); - } - } } catch (e) { console.log(e); err("problem receiving lightning payment", e.message); diff --git a/routes/liquid/broadcast.js b/routes/liquid/broadcast.js deleted file mode 100644 index 820c0be1..00000000 --- a/routes/liquid/broadcast.js +++ /dev/null @@ -1,58 +0,0 @@ -import { emit } from "$lib/sockets"; -import store from "$lib/store"; -import { requirePin } from "$lib/utils"; - -export default async (req, res) => { - try { - await requirePin(req); - - const { user } = req; - let hash = await lq.sendRawTransaction(req.body.tx); - - if (user) { - await db.transaction(async transaction => { - let account = await db.Account.findOne({ - where: { - id: user.account_id - }, - lock: transaction.LOCK.UPDATE, - transaction - }); - - let payment = await db.Payment.create( - { - ...req.body.payment, - hash, - amount: -req.body.payment.amount, - tip: 0, - account_id: account.id, - user_id: user.id, - rate: store.rates[user.currency], - currency: user.currency, - confirmed: true, - received: false, - network: "liquid" - }, - { transaction } - ); - - await account.increment( - { balance: payment.amount - payment.fee }, - { transaction } - ); - await account.reload({ transaction }); - - payment = payment.get({ plain: true }); - payment.account = account.get({ plain: true }); - emit(user.username, "payment", payment); - emit(user.username, "account", payment.account); - res.send(payment); - }); - } else { - res.send(hash); - } - } catch (e) { - err("problem broadcasting liquid transaction", e); - res.code(500).send(e.message); - } -}; diff --git a/routes/liquid/conversionFee.js b/routes/liquid/conversionFee.js deleted file mode 100644 index a25c2e7d..00000000 --- a/routes/liquid/conversionFee.js +++ /dev/null @@ -1,12 +0,0 @@ -// Code for the conversion fee -// This is in a separate file so that the same function can be used by ./send and ./receive - -const conversionFeeMultiplier = 0.001; -export const conversionFeeReceiver = "coinosfees"; // account that receives the fees - -/** - * Returns the fee you have to pay for withdrawing 'amount' L-BTC. - */ -export function computeConversionFee(amount) { - return Math.floor(amount * conversionFeeMultiplier); -} diff --git a/routes/liquid/fee.js b/routes/liquid/fee.js deleted file mode 100644 index ae6e4393..00000000 --- a/routes/liquid/fee.js +++ /dev/null @@ -1,89 +0,0 @@ -import config from "$config"; -import db from "$db"; -import { emit } from "$lib/sockets"; -import buildTx from "$lib/buildliquidtx"; -import { err, l } from "$lib/logging"; -import { SATS } from "$lib/utils"; -import lq from "$lib/liquid"; - -export const liquidTx = async ({ address, asset, amount, feeRate, replaceable, user }) => { - let tx, fee; - - if (user.account.pubkey) { - let psbt = await buildTx({ - address, - asset, - amount, - feeRate, - replaceable, - user - }); - return psbt; - } - - let invoice = await db.Invoice.findOne({ - where: { address }, - include: { - attributes: ["username"], - model: db.User, - as: "user" - } - }); - - if (invoice) { - let { ismine } = await lq.getAddressInfo(address); - if (ismine) { - emit(user.username, "to", invoice.user); - return; - } - } - - if (config.liquid.walletpass) - await lq.walletPassphrase(config.liquid.walletpass, 300); - - amount = parseInt(amount); - - let params = { - subtractFeeFromOutputs: amount === user.balance ? [0] : [] - }; - - let value = (amount / SATS).toFixed(8); - if (feeRate) params.feeRate = (feeRate / SATS).toFixed(8); - - let info = await lq.getAddressInfo(address); - - tx = await lq.createRawTransaction( - [], - [ - { - [address]: (amount / SATS).toFixed(8), - asset - } - ], - 0, - replaceable - ); - - l("funding tx for fee estimate", tx); - - tx = await lq.fundRawTransaction(tx, params); - - let blinded = await lq.blindRawTransaction(tx.hex); - let signed = await lq.signRawTransactionWithWallet(blinded); - - let decoded = await lq.decodeRawTransaction(signed.hex); - feeRate = Math.round((tx.fee * SATS * 1000) / decoded.vsize); - l("estimated", asset, feeRate); - - return { feeRate, tx }; -}; - -export default async (req, res) => { - try { - let tx = await liquidTx({ ...req.body, user: req.user }); - res.send(tx); - } catch (e) { - err("error estimating liquid fee", e.message, e.stack); - return res.code(500).send(e.message); - } -}; diff --git a/routes/liquid/generate.js b/routes/liquid/generate.js deleted file mode 100644 index 423d8b6a..00000000 --- a/routes/liquid/generate.js +++ /dev/null @@ -1,5 +0,0 @@ -export default async (req, res) => { - let address = await lq.getNewAddress(); - await lq.generateToAddress(1, address); - res.send({}); -}; diff --git a/routes/liquid/index.js b/routes/liquid/index.js deleted file mode 100644 index c4c50021..00000000 --- a/routes/liquid/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import broadcast from "./broadcast"; -import generate from "./generate"; -import fee from "./fee"; -import send from "./send"; - -export default { - broadcast, - generate, - fee, - send, -}; diff --git a/routes/liquid/receive.js b/routes/liquid/receive.js deleted file mode 100644 index 718feba5..00000000 --- a/routes/liquid/receive.js +++ /dev/null @@ -1,406 +0,0 @@ -import db from "$db"; -import config from "$config"; -import { getBlockHeight, getUser, getUserById, toSats, SATS } from "$lib/utils"; -import { emit } from "$lib/sockets"; -import store from "$lib/store"; -import { sendLiquid } from "$routes/liquid/send"; -import { notify } from "$lib/notifications"; -import { callWebhook } from "$lib/webhooks"; -import reverse from "buffer-reverse"; -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 { l, err, warn } from "$lib/logging"; -import lq from "$lib/liquid"; - -const { Block, networks, Transaction } = liquidJs; - -const network = - networks[ - config.liquid.network === "mainnet" ? "liquid" : config.liquid.network - ]; - -import { computeConversionFee } from "./conversionFee"; - -const getAccount = async (params, transaction) => { - let account = await db.Account.findOne({ - where: params, - lock: transaction.LOCK.UPDATE, - transaction - }); - - if (account) { - l("found account", params, account.asset, account.id); - return account; - } - - let { asset, pubkey } = params; - let name = asset.substr(0, 6); - let domain = ""; - let ticker = asset.substr(0, 3).toUpperCase(); - let precision = 8; - - if (pubkey) { - const nc = await db.Account.findOne({ - where: { pubkey }, - order: [["id", "ASC"]], - limit: 1, - lock: transaction.LOCK.UPDATE, - transaction - }); - - params.index = 0; - params.path = nc.path; - params.pubkey = nc.pubkey; - params.privkey = nc.privkey; - params.seed = nc.seed; - } - - 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) { - l("existing", existing.id); - ({ domain, ticker, precision, name } = existing); - } - } - - params = { ...params, ...{ domain, ticker, precision, name } }; - params.balance = 0; - params.pending = 0; - params.network = "liquid"; - return db.Account.create(params, { transaction }); -}; - -const zmqRawBlock = zmq.socket("sub"); -zmqRawBlock.connect(config.liquid.zmqrawblock); -zmqRawBlock.subscribe("rawblock"); - -const zmqRawTx = zmq.socket("sub"); -zmqRawTx.connect(config.liquid.zmqrawtx); -zmqRawTx.subscribe("rawtx"); - -const queue = {}; -const seen = []; - -zmqRawTx.on("message", async (topic, message, sequence) => { - try { - const hex = message.toString("hex"); - - let unblinded, tx, blinded, txid; - try { - txid = Transaction.fromHex(hex).getId(); - if (seen.includes(txid)) return; - seen.push(txid); - if (seen.length > 5000) seen.shift(); - - unblinded = await lq.unblindRawTransaction(hex); - tx = await lq.decodeRawTransaction(unblinded.hex); - blinded = await lq.decodeRawTransaction(hex); - } catch (e) { - return err("problem decoding liquid tx", e.message); - } - - for (let o of tx.vout) { - try { - if (!(o.scriptPubKey && o.scriptPubKey.address)) return; - - const { asset } = o; - const value = toSats(o.value); - const { address } = o.scriptPubKey; - - if ( - Object.keys(store.addresses).includes(address) && - !store.change.includes(address) - ) { - await db.transaction(async transaction => { - let user = await getUser(store.addresses[address], transaction); - - let invoice = await db.Invoice.findOne({ - where: { - [Op.or]: { - unconfidential: address, - address - }, - user_id: user.id, - network: "liquid" - }, - order: [["id", "DESC"]], - transaction - }); - - if (!invoice) return; - - let confirmed = 0; - - let account = await db.Account.findOne({ - where: { - id: invoice.account_id, - asset - }, - lock: transaction.LOCK.UPDATE, - transaction - }); - - if (!account) { - let pubkey = account ? account.pubkey : null; - - account = await getAccount( - { - user_id: user.id, - asset, - pubkey - }, - transaction - ); - } - - await account.increment({ pending: value }, { transaction }); - await account.reload({ transaction }); - - if (config.liquid.walletpass) - await lq.walletPassphrase(config.liquid.walletpass, 300); - - await user.save({ transaction }); - - const currency = invoice ? invoice.currency : user.currency; - const rate = invoice ? invoice.rate : store.rates[user.currency]; - const tip = invoice ? invoice.tip : 0; - const memo = invoice ? invoice.memo : ""; - - let payment = await db.Payment.create( - { - account_id: account.id, - user_id: user.id, - hash: blinded.txid, - amount: value - tip, - currency, - memo, - rate, - received: true, - tip, - confirmed, - address, - network: "liquid", - invoice_id: invoice.id - }, - { transaction } - ); - - payment = payment.get({ plain: true }); - payment.account = account.get({ plain: true }); - payment.invoice = invoice.get({ plain: true }); - - emit(user.username, "payment", payment); - emit(user.username, "account", payment.account); - l("liquid detected", address, user.username, asset, value); - notify(user, `${value} SAT payment detected`); - callWebhook(invoice, payment); - }); - } - } catch (e) { - console.log(e); - err("Problem processing transaction", e.message, e.stack); - } - } - } catch (e) { - console.log(e); - } -}); - -zmqRawBlock.on("message", async (topic, message, sequence) => { - try { - const payments = await db.Payment.findAll({ - where: { confirmed: 0 } - }); - - let hash, json; - - hash = await lq.getBlockHash(getBlockHeight(message)); - json = await lq.getBlock(hash, 2); - - for (let tx of json.tx) { - if (store.issuances[tx.txid]) { - await db.transaction(async transaction => { - const { - user_id, - asset, - asset_amount, - asset_payment_id, - token, - token_amount, - token_payment_id - } = store.issuances[tx.txid]; - - const user = await getUserById(user_id); - - let account = await db.Account.findOne({ - where: { user_id, asset }, - lock: transaction.LOCK.UPDATE, - transaction - }); - account.balance = Math.round(asset_amount * SATS); - account.pending = 0; - await account.save({ transaction }); - - let payment = await db.Payment.findOne({ - where: { id: asset_payment_id }, - include: { - model: db.Account, - as: "account" - }, - lock: transaction.LOCK.UPDATE, - transaction - }); - - payment.confirmed = true; - await payment.save({ transaction }); - payment = payment.get({ plain: true }); - payment.account = account.get({ plain: true }); - - emit(user.username, "account", account); - emit(user.username, "payment", payment); - - if (token) { - account = await db.Account.findOne({ - where: { user_id, asset: token }, - lock: transaction.LOCK.UPDATE, - transaction - }); - account.balance = token_amount * SATS; - account.pending = 0; - await account.save({ transaction }); - - payment = await db.Payment.findOne({ - where: { id: token_payment_id }, - include: { - model: db.Account, - as: "account" - }, - lock: transaction.LOCK.UPDATE, - transaction - }); - - payment.confirmed = true; - await payment.save({ transaction }); - - payment = payment.get({ plain: true }); - payment.account = account.get({ plain: true }); - - emit(user.username, "account", account); - emit(user.username, "payment", payment); - } - }); - } else if (payments.find(p => p.hash === tx.txid)) queue[tx.txid] = 1; - } - } catch (e) { - return console.log(e); - } -}); - -setInterval(async () => { - try { - const arr = Object.keys(queue); - - for (let i = 0; i < arr.length; i++) { - const hash = arr[i]; - - let account, invoice, address, user, total, p; - await db.transaction(async transaction => { - p = await db.Payment.findOne({ - where: { hash, confirmed: 0, received: 1 }, - include: [ - { - model: db.Account, - as: "account" - }, - { - model: db.Invoice, - as: "invoice" - }, - { - model: db.User, - as: "user" - } - ], - lock: transaction.LOCK.UPDATE, - transaction - }); - - if (p && p.address) address = p.address; - if (p && p.user) user = p.user; - - if (p && p.account) { - ({ account, invoice } = p); - total = p.amount + p.tip; - p.confirmed = 1; - await account.save({ transaction }); - - await invoice.increment({ received: total }, { transaction }); - await account.increment({ balance: total }, { transaction }); - await account.decrement( - { pending: Math.min(account.pending, total) }, - { transaction } - ); - await account.increment( - { liquid_credits: computeConversionFee(total) }, - { transaction } - ); - await account.reload({ transaction }); - await invoice.reload({ transaction }); - - await p.save({ transaction }); - - p = p.get({ plain: true }); - account = account.get({ plain: true }); - invoice = invoice.get({ plain: true }); - - emit(user.username, "account", account); - emit(user.username, "payment", p); - - l("liquid confirmed", user.username, account.asset, p.amount, p.tip); - - notify(user, `${total} SAT payment confirmed`); - callWebhook(p.invoice, p); - } else { - warn("couldn't find liquid payment", hash); - } - - delete queue[hash]; - }); - - let c = store.convert[address]; - if (address && c && p.account.asset === network.assetHash) { - l( - "liquid detected for conversion request", - address, - c.address, - user.username - ); - - user.account = p.account; - - sendLiquid({ - address: c.address, - amount: total - 100, - user, - limit: total - }); - } - } - } catch (e) { - err("problem processing queued liquid transaction", e.message, e.stack); - } -}, 1000); diff --git a/routes/liquid/send.js b/routes/liquid/send.js deleted file mode 100644 index 83b5d6ca..00000000 --- a/routes/liquid/send.js +++ /dev/null @@ -1,276 +0,0 @@ -import db from "$db"; -import lq from "$lib/liquid"; -import { toSats } from "$lib/utils"; -import { emit } from "$lib/sockets"; -import store from "$lib/store"; -import config from "$config"; -import { l, warn, err } from "$lib/logging"; -import { liquidTx } from "$routes/liquid/fee"; -import { Transaction } from 'liquidjs-lib'; -import { requirePin } from "$lib/utils"; - -import { - computeConversionFee, - conversionFeeReceiver -} from "./conversionFee.js"; - -const btc = config.liquid.btcasset; -const lcad = config.liquid.cadasset; - -export const sendLiquid = async ({ asset, amount, user, address, memo, tx, limit }) => { - try { - l("sending liquid", amount, address); - if (!tx) { - ({ tx } = await liquidTx({ - address, - asset, - amount, - feeRate: 200, - replaceable: false, - user - })); - } - - const isChange = async address => - (await lq.getAddressInfo(address)).ismine && - !Object.keys(store.addresses).includes(address); - - let totals = {}; - let change = {}; - let fee = 0; - - let { vout } = await lq.decodeRawTransaction(tx.hex); - - for (let i = 0; i < vout.length; i++) { - let { - asset, - value, - scriptPubKey - } = vout[i]; - - if (scriptPubKey.type === "fee") fee = toSats(value); - else { - if (!totals[asset]) totals[asset] = change[asset] = 0; - totals[asset] += toSats(value); - - if (scriptPubKey.address) { - if (await isChange(scriptPubKey.address)) { - change[asset] += toSats(value); - } - } - } - } - - const assets = Object.keys(totals); - const payments = []; - - let main, signed; - await db.transaction(async transaction => { - let fee_payment; - let fee_payment_id = null; - - for (let i = 0; i < assets.length; i++) { - let asset = assets[i]; - let amount = totals[asset]; - if (change[asset]) amount -= change[asset]; - let total = amount; - - if (asset === btc) { - let covered = 0; - let nonbtc = assets.filter(a => a !== btc); - if (nonbtc.length === 1) { - let faucet = await db.Account.findOne({ - where: { - asset: nonbtc[0], - user_id: null - }, - lock: transaction.LOCK.UPDATE, - transaction - }); - - if (faucet) { - covered = faucet.balance; - if (covered > fee) covered = fee; - await faucet.decrement({ balance: covered }, { transaction }); - await faucet.reload({ transaction }); - await faucet.save({ transaction }); - } - } - - total += fee - covered; - } - - var conversionFee = computeConversionFee(amount); - - if (limit && total > limit + fee) - throw new Error("Tx amount exceeds authorized amount"); - - if (asset !== btc || total) { - l("creating liquid payment", user.username, asset, total, fee); - - let account = await db.Account.findOne({ - where: { - user_id: user.id, - asset, - pubkey: null - }, - lock: transaction.LOCK.UPDATE, - order: [["balance", "DESC"]], - transaction - }); - - // use user's credits to reduce fee, if available - let conversionFeeDeduction = Math.min( - account.liquid_credits, - conversionFee - ); - if (conversionFeeDeduction) { - await account.decrement( - { liquid_credits: conversionFeeDeduction }, - { transaction } - ); - await account.reload({ transaction }); - conversionFee -= conversionFeeDeduction; - } - - if (asset !== btc) conversionFee = 0; - - if (total > account.balance) { - warn("amount exceeds balance", { - total, - fee, - balance: account.balance - }); - throw new Error( - `Insufficient funds, need ${total} ${ - account.ticker === "BTC" ? "SAT" : account.ticker - }, have ${account.balance}` - ); - } else if (total + conversionFee > account.balance) { - warn("amount plus conversion fee exceeds balance", { - total, - fee, - conversionFee, - balance: account.balance - }); - throw new Error( - `Insufficient funds, need ${total + conversionFee} ${ - account.ticker === "BTC" ? "SAT" : account.ticker - }, have ${account.balance}` - ); - } - - await account.decrement( - { balance: total + conversionFee }, - { transaction } - ); - await account.reload({ transaction }); - - let receiverAccount = await db.Account.findOne({ - where: { - "$user.username$": conversionFeeReceiver - }, - include: [ - { - model: db.User, - as: "user" - } - ], - lock: transaction.LOCK.UPDATE, - transaction - }); - - if (conversionFee) { - await receiverAccount.increment( - { balance: conversionFee }, - { transaction } - ); - await receiverAccount.reload({ transaction }); - fee_payment = { - amount: conversionFee, - fee: 0, - memo: "Liquid conversion fee", - account_id: receiverAccount.id, - user_id: receiverAccount.user_id, - rate: store.rates[receiverAccount.user.currency], - currency: receiverAccount.user.currency, - confirmed: true, - received: true, - network: "COINOS" - }; - fee_payment.account = receiverAccount; - ({ id: fee_payment_id } = await db.Payment.create(fee_payment, { - transaction - })); - } - - let payment = { - amount: -amount, - account_id: account.id, - fee, - memo, - user_id: user.id, - rate: store.rates[user.currency], - currency: user.currency, - address, - confirmed: true, - received: false, - network: "liquid", - fee_payment_id - }; - - payment.account = account; - payments.push(payment); - } - } - - signed = await lq.signRawTransactionWithWallet( - await lq.blindRawTransaction(tx.hex) - ); - let txid = Transaction.fromHex(signed.hex).getId(); - - for (let i = 0; i < payments.length; i++) { - let p = payments[i]; - if (p) { - let { account } = p; - p.hash = txid; - p = await db.Payment.create(p, { transaction }); - if (p.user_id === user.id && (account.ticker !== "BTC" || !main)) { - main = p.get({ plain: true }); - main.fee_payment = fee_payment; - main.account = account.get({ plain: true }); - } - } - } - - emit(user.username, "account", main.account); - emit(user.username, "payment", main); - }); - - if (config.liquid.walletpass) - await lq.walletPassphrase(config.liquid.walletpass, 300); - - let txid = await lq.sendRawTransaction(signed.hex); - l("sent liquid tx", txid, address); - - return main; - } catch (e) { - err("problem sending liquid", e.message); - return e; - } -}; - -export default async (req, res) => { - let { user } = req; - - try { - await requirePin(req); - let result = await sendLiquid({ ...req.body, user }) - if (result instanceof Error) throw result; - res.send(result); - } catch (e) { - console.log(e) - err("problem sending liquid", user.username, e.message); - return res.code(500).send(e); - } -}; diff --git a/routes/lnurl.js b/routes/lnurl.js index 1ad7b814..e9a0b167 100644 --- a/routes/lnurl.js +++ b/routes/lnurl.js @@ -1,6 +1,5 @@ import app from "$app"; import config from "$config"; -import db from "$db"; import store from "$lib/store"; import { optionalAuth, auth } from "$lib/passport"; import { l, err, warn } from "$lib/logging"; @@ -9,11 +8,9 @@ import ln from "$lib/ln"; import persist from "$lib/persist"; import { emit } from "$lib/sockets"; -import axios from "axios"; import lnurl from "lnurl"; import jwt from "jsonwebtoken"; import qs from "query-string"; -import bolt11 from "bolt11"; import { computeConversionFee, diff --git a/routes/locations.js b/routes/locations.js index 63bfe1a1..1cebc883 100644 --- a/routes/locations.js +++ b/routes/locations.js @@ -1,19 +1,21 @@ import app from "$app"; import redis from "$lib/redis"; -import axios from "axios"; +import got from "got"; -let locations; +let locations = []; app.get("/locations", async (req, res) => { res.send({ locations }); }); let getLocations = async () => { - let { data } = await axios.get( + try { + let r = await got( "https://api.btcmap.org/v2/elements?updated_since=2022-09-19" - ); - locations = data.filter( + ).json(); + locations = r.filter( l => l["osm_json"].tags && l["osm_json"].tags["payment:coinos"] === "yes" ); + } catch(e) {} setTimeout(getLocations, 60000); }; diff --git a/routes/payments.js b/routes/payments.js index 279588d1..7b094ccc 100644 --- a/routes/payments.js +++ b/routes/payments.js @@ -1,279 +1,171 @@ import app from "$app"; -import db from "$db"; import config from "$config"; import store from "$lib/store"; -import getAccount from "$lib/account"; import { auth, adminAuth, optionalAuth } from "$lib/passport"; import fs from "fs"; import { join } from "path"; -import { Op } from "@sequelize/core"; import send from "./send"; import { err, l, warn } from "$lib/logging"; import { fail } from "$lib/utils"; import { emit } from "$lib/sockets"; -import bc from "$lib/bitcoin"; -import lq from "$lib/liquid"; - -import btcRoutes from "./bitcoin/index"; import lnRoutes from "./lightning/index"; -import lqRoutes from "./liquid/index"; - -import "./bitcoin/receive"; import "./lightning/receive"; -import "./liquid/receive"; app.post("/send", auth, send); -app.post("/sendToTokenHolders", auth, async (req, res, next) => { - let { asset, amount } = req.body; - - let accounts = await db.Account.findAll({ - where: { - asset, - "$user.username$": { [Op.ne]: "gh" } - }, - - include: [{ model: db.User, as: "user" }] - }); - - let totalTokens = accounts.reduce((a, b) => a + b.balance, 0); - - let totalSats = Math.floor(amount / totalTokens); - - if (totalSats < 1) throw new Error("amount is too low to distribute"); - - for (let i = 0; i < accounts.length; i++) { - let account = accounts[i]; - console.log(account.user.username, account.balance); - } - accounts.map(({ balance, user: { username } }) => ({ username, balance })); - - res.send({ success: "it worked" }); -}); - -app.get("/except", adminAuth, (req, res) => { - let s = fs.createWriteStream("exceptions", { flags: "a" }); - store.unaccounted.map(tx => s.write(tx.txid + "\n")); - l("updated exceptions"); - res.send("updated exceptions"); -}); app.post("/lightning/parse", lnRoutes.parse); -app.post("/lightning/channel", lnRoutes.channel); app.post("/lightning/query", auth, lnRoutes.query); app.post("/lightning/send", auth, lnRoutes.send); -if (config.lnurl) { - let { channelRequest } = await import("$routes/lightning/channelRequest"); - app.post("/lightning/channelRequest", channelRequest); -} - -app.post("/bitcoin/broadcast", optionalAuth, btcRoutes.broadcast); -app.get("/bitcoin/generate", auth, btcRoutes.generate); -app.post("/bitcoin/sweep", auth, btcRoutes.sweep); -app.post("/bitcoin/fee", auth, btcRoutes.fee); -app.post("/bitcoin/send", auth, btcRoutes.send); +app.get( + "/payments", + auth, + async ({ uuid, query: { start, end, limit, offset } }, res) => { + if (limit) limit = parseInt(limit); + if (offset) offset = parseInt(offset); -setTimeout(async () => { - try { - const address = await bc.getNewAddress(); - const { hdkeypath } = await bc.getAddressInfo(address); - const parts = hdkeypath.split("/"); - store.bcAddressIndex = parts[parts.length - 1].replace("'", ""); - } catch (e) { - console.error(e); - } -}, 50); - -app.post("/liquid/broadcast", optionalAuth, lqRoutes.broadcast); -app.get("/liquid/generate", auth, lqRoutes.generate); -app.post("/liquid/fee", auth, lqRoutes.fee); -app.post("/liquid/send", auth, lqRoutes.send); - -setTimeout(async () => { - try { - const address = await lq.getNewAddress(); - const { hdkeypath } = await lq.getAddressInfo(address); - const parts = hdkeypath.split("/"); - store.lqAddressIndex = parts[parts.length - 1].slice(0, -1); - } catch (e) { - warn("Problem getting liquid address index", e.message); - } -}, 50); - -app.get("/payments", auth, async (req, res) => { - let { start, end, limit, offset, v2 } = req.query; - - if (limit) limit = parseInt(limit); - if (offset) offset = parseInt(offset); + // if (start || end) where.createdAt = {}; + // if (start) where.createdAt[Op.gte] = new Date(parseInt(start)); + // if (end) where.createdAt[Op.lte] = new Date(parseInt(end)); - let where = { - account_id: req.user.account_id - }; - - if (start || end) where.createdAt = {}; - if (start) where.createdAt[Op.gte] = new Date(parseInt(start)); - if (end) where.createdAt[Op.lte] = new Date(parseInt(end)); - - if (!req.user.account_id) return res.send([]); - - let total = await db.Payment.count({ where }); - - let payments = await db.Payment.findAll({ - where, - order: [["id", "DESC"]], - include: { - model: db.Account, - as: "account" - }, - include: { - attributes: ["username", "profile", "uuid"], - model: db.User, - as: "with" - }, - limit, - offset - }); - - if (v2) { - res.send({ transactions: payments, total }); - } else { - res.send(payments); + let payments = await redis.get(`${uuid}:payments`); + res.send({ payments, total }); } -}); - -app.get("/voucher/:redeemcode", async (req, res) => { - try { - const { redeemcode } = req.params; - let payment = await db.Payment.findOne({ - where: { - redeemcode - }, - include: { all: true } - }); +); - payment = payment.get({ plain: true }); - payment.redeemer = payment["with"]; +// app.get("/voucher/:redeemcode", async (req, res) => { +// try { +// const { redeemcode } = req.params; +// let payment = await db.Payment.findOne({ +// where: { +// redeemcode +// }, +// include: { all: true } +// }); +// +// payment = payment.get({ plain: true }); +// payment.redeemer = payment["with"]; +// +// if (!payment) fail("invalid code"); +// +// res.send(payment); +// } catch (e) { +// res.code(500).send(e.message); +// } +// }); - if (!payment) fail("invalid code"); - - res.send(payment); - } catch (e) { - res.code(500).send(e.message); - } -}); - -let redeeming = {}; -app.post("/redeem", optionalAuth, async function(req, res) { - const { redeemcode } = req.body; - try { - await db.transaction(async transaction => { - if (redeeming[redeemcode]) fail("redemption in progress"); - redeeming[redeemcode] = true; - if (!redeemcode) fail("no code provided"); - - let { user } = req; - - const source = await db.Payment.findOne({ - where: { - redeemcode: req.body.redeemcode - }, - include: { - model: db.Account, - as: "account" - }, - lock: transaction.LOCK.UPDATE, - transaction - }); - - l("redeeming", redeemcode); - - if (!source) fail("Invalid code"); - if (source.redeemed) fail("Voucher has already been redeemed"); - let { amount } = source; - amount = -amount; - - if (!user) { - const ip = - req.headers["x-forwarded-for"] || req.connection.remoteAddress; - - user = await register( - { - username: redeemcode.substr(0, 8), - password: "" - }, - ip, - false - ); - - let payload = { username: user.username }; - let token = jwt.sign(payload, config.jwt); - res.cookie("token", token, { - expires: new Date(Date.now() + 432000000) - }); - - delete redeeming[redeemcode]; - return res.send({ user }); - } - - let account = await getAccount(source.account.asset, user, transaction); - let { hash, memo, confirmed, fee, network } = source; - - source.redeemed = true; - (source.with_id = user.id), await source.save({ transaction }); - - let payment = await db.Payment.create( - { - amount, - account_id: account.id, - user_id: user.id, - hash: "Voucher " + redeemcode, - memo, - rate: store.rates[user.currency], - currency: user.currency, - confirmed, - network, - received: true, - fee, - with_id: source.user_id - }, - { transaction } - ); - - await account.increment({ balance: amount }, { transaction }); - await account.reload({ transaction }); - - payment = payment.get({ plain: true }); - payment.account = account.get({ plain: true }); - emit(user.username, "payment", payment); - emit(user.username, "account", account); - - res.send({ payment }); - }); - } catch (e) { - delete redeeming[redeemcode]; - console.log(e); - err("problem redeeming", e.message); - return res.code(500).send("There was a problem redeeming the voucher"); - } -}); - -app.post("/checkRedeemCode", auth, async function(req, res) { - const { redeemcode } = req.body; - - const payment = await db.Payment.findOne({ where: { redeemcode } }); - res.send(payment); -}); - -app.get("/payments/:hash", auth, async function(req, res) { - try { - let payment = await db.Payment.findOne({ - where: { user_id: req.user.id, hash: req.params.hash } - }); - - return payment.get({ plain: true }); - } catch (e) { - console.log(e); - } -}); +// let redeeming = {}; +// app.post("/redeem", optionalAuth, async function(req, res) { +// const { redeemcode } = req.body; +// try { +// await db.transaction(async transaction => { +// if (redeeming[redeemcode]) fail("redemption in progress"); +// redeeming[redeemcode] = true; +// if (!redeemcode) fail("no code provided"); +// +// let { user } = req; +// +// const source = await db.Payment.findOne({ +// where: { +// redeemcode: req.body.redeemcode +// }, +// include: { +// model: db.Account, +// as: "account" +// }, +// lock: transaction.LOCK.UPDATE, +// transaction +// }); +// +// l("redeeming", redeemcode); +// +// if (!source) fail("Invalid code"); +// if (source.redeemed) fail("Voucher has already been redeemed"); +// let { amount } = source; +// amount = -amount; +// +// if (!user) { +// const ip = +// req.headers["x-forwarded-for"] || req.connection.remoteAddress; +// +// user = await register( +// { +// username: redeemcode.substr(0, 8), +// password: "" +// }, +// ip, +// false +// ); +// +// let payload = { username: user.username }; +// let token = jwt.sign(payload, config.jwt); +// res.cookie("token", token, { +// expires: new Date(Date.now() + 432000000) +// }); +// +// delete redeeming[redeemcode]; +// return res.send({ user }); +// } +// +// let account = await getAccount(source.account.asset, user, transaction); +// let { hash, memo, confirmed, fee, network } = source; +// +// source.redeemed = true; +// (source.with_id = user.id), await source.save({ transaction }); +// +// let payment = await db.Payment.create( +// { +// amount, +// account_id: account.id, +// user_id: user.id, +// hash: "Voucher " + redeemcode, +// memo, +// rate: store.rates[user.currency], +// currency: user.currency, +// confirmed, +// network, +// received: true, +// fee, +// with_id: source.user_id +// }, +// { transaction } +// ); +// +// await account.increment({ balance: amount }, { transaction }); +// await account.reload({ transaction }); +// +// payment = payment.get({ plain: true }); +// payment.account = account.get({ plain: true }); +// emit(user.username, "payment", payment); +// emit(user.username, "account", account); +// +// res.send({ payment }); +// }); +// } catch (e) { +// delete redeeming[redeemcode]; +// console.log(e); +// err("problem redeeming", e.message); +// return res.code(500).send("There was a problem redeeming the voucher"); +// } +// }); +// +// app.post("/checkRedeemCode", auth, async function(req, res) { +// const { redeemcode } = req.body; +// +// const payment = await db.Payment.findOne({ where: { redeemcode } }); +// res.send(payment); +// }); +// +// app.get("/payments/:hash", auth, async function(req, res) { +// try { +// let payment = await db.Payment.findOne({ +// where: { user_id: req.user.id, hash: req.params.hash } +// }); +// +// return payment.get({ plain: true }); +// } catch (e) { +// console.log(e); +// } +// }); diff --git a/routes/referrals.js b/routes/referrals.js deleted file mode 100644 index 7193964d..00000000 --- a/routes/referrals.js +++ /dev/null @@ -1,272 +0,0 @@ -import express from "express"; -var router = express.Router(); - -import { v4 } from "uuid"; - -router.get("/", function(req, res, next) { - res.send("list of referrals..."); -}); - -// Grant & Retrieve Referral tokens for existing user - -// usage: GET '/grant?sponsor_id=N&expiry=2021-09-01' -// -// Required: -// sponsor_id (= user.id) -// Changes to DB: -// adds record to referrals table: -// { token: <uuid>, sponsor_id: <user.id>, status: "available" } - -/** - * @api {post} /grant Grant Referral code - * @apiName Grant - * @apiGroup Referrals - * - * - * @apiPermission member - * - * @apiParam {sponsor_id} user_id of member generating referral code. - * @apiParam {expiry} optional expiry date for token. - * - * @apiSuccess {String} token referral token - * @apiSuccess {Date} expiry optional expiry - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "token": "*********", - * "status": "available", - * "expiry": null - * } - */ -router.post("/grant", auth, async (req, res) => { - const { sponsor_id, expiry } = req.body; - var token = v4(); - - var ref = await knex.table("referrals").insert({ - sponsor_id: sponsor_id, - token: token, - status: "available" - }); - - return res.send({ token: token, status: "available", expiry: expiry }); -}); - -/** - * @api {get} /grant Grant Referral code (variation using get) - * @apiName Grant - * @apiGroup Referrals - * - * @apiPermission member - * - * @apiPermission member - * @apiParam {sponsor_id} user_id of member generating referral code. - * @apiParam {expiry} optional expiry date for token. - * - * @apiSuccess {String} token referral token - * @apiSuccess {Status} status referral status - * @apiSuccess {Date} expiry optional expiry - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "token": "*********", - * "status": "available", - * "expiry": null - * } - */ -router.get("/grant", auth, async (req, res) => { - const { sponsor_id, expiry } = req.query; - var token = v4(); - - var ref = await knex.table("referrals").insert({ - sponsor_id: sponsor_id, - token: token, - status: "available" - }); - - return res.send({ token: token, status: "available", expiry: expiry }); -}); - -/** - * @api {get} /checkTokens/:sponsor_id Retrieve list of referral tokens generated by this user - * - * @apiName checkTokens - * @apiGroup Referrals - * - * - * @apiPermission member - * - * @apiParam {sponsor_id} user_id of member generating referral code. - * @apiParam {status} optional token status. - * - * @apiSuccess tokens array of token objects - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "tokens": [ - * {"token": "*********", "status": "available", "expiry": null} - * ] - * } - */ - -// TO FIX - change sponsor_id so that it is retrieved from current payload (NOT FROM URL) - -router.get("/checkTokens/:sponsor_id", auth, async (req, res) => { - const { sponsor_id } = req.params; - const { status } = req.query; - - var tokens = knex - .select( - "token", - "created_at", - knex.raw("LEFT(created_at,10) as created"), - "username", - "status" - ) - .from("referrals") - .leftJoin("users", "users.id", "referrals.user_id") - .where("sponsor_id", "like", sponsor_id); - - if (status && status !== "all") - tokens = tokens.where("status", "like", status); - const found = await tokens; - - return res.send({ tokens: found }); -}); - -/** - * @api {get} /verify/:user_id Verify token and apply to existing user - * - * @apiName verify - * @apiGroup Referrals - * - * @apiPermission member - * - * @apiParam {user_id} Current user - * @apiParam {token} Token to validate - * - * @apiSuccess verified = true - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "verified": true - * } - * - * @apiDescription referral token is updated from 'available' to 'used' - * @apiDescription referral token is updated with existing user_id - */ -router.get("/verify/:user_id/:token", auth, async (req, res) => { - const { user_id, token } = req.params; - - const found = await knex - .select("status") - .from("referrals") - .where("token", "like", token) - .whereNull("user_id"); - - if (found && found.length) { - if (found[0].status === "available") { - await knex("referrals") - .where("token", "like", token) - .update({ - status: "used", - user_id: user_id, - updated_at: new Date().toISOString().substring(0, 10) - }); - - return res.send({ - verified: true, - sponsor_id: found.sponsor_id, - updated: found.updated_at - }); - } else { - res.code(500).send({ - verified: false, - message: "Referral already " + found[0].status - }); - } - } else { - res.code(500).send("Invalid referral token"); - } -}); - -/** - * @api {post} /joinQueue Join waiting list (track email & phone) - * @apiName joinQueue - * @apiGroup Referrals - * - * @apiParam {email} email address - * @apiParam {phone} phone number - * - * @apiSuccess verified = true - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "success": true - * "message": 'Added to waiting list' - * } - * - * @apiDescription referral token is updated from 'available' to 'used' - * @apiDescription referral token is updated with existing user_id - */ -router.post("/joinQueue", async (req, res) => { - const { email, phone, user_id } = req.body; - - await knex.table("waiting_list").insert({ - email: email, - phone: phone, - user_id: user_id - }); - - res.send({ - success: true, - message: "Added " + email + " to waiting list " + phone - }); -}); - -router.get("/joinQueue", async (req, res) => { - const { email, phone, user_id } = req.query; - - await knex("waiting_list").insert({ - email: email, - phone: phone, - user_id: user_id - }); - - res.send({ - success: true, - message: "Added " + email + " to waiting list " + phone - }); -}); - -/** - * @api {get} /isReferred/:user_id Check if user is referred - * @apiName isReferred - * @apiGroup Referrals - * - * @apiParam {user_id} user id - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * true - * } - * - * @apiDescription returns boolean indicating if user is referred or not - */ -router.get("/isReferred/:user_id", async (req, res) => { - const { user_id } = req.params; - - var referred = await knex("referrals") - .select("referrals.id") - .where("user_id", "like", user_id) - .where("status", "like", "used"); - - if (referred && referred.length) { - res.send({ referred: true }); - } else { - res.send({ referred: false }); - } -}); - -export default router; diff --git a/routes/requests.js b/routes/requests.js index 70ab2b07..0b3832e8 100644 --- a/routes/requests.js +++ b/routes/requests.js @@ -1,8 +1,6 @@ import app from "$app"; -import db from "$db"; import { auth } from "$lib/passport"; import { emit } from "$lib/sockets"; -import { Op, col } from "@sequelize/core"; app.get("/request/:id", auth, async (req, res) => { try { diff --git a/routes/swaps.js b/routes/swaps.js deleted file mode 100644 index 169c2b8f..00000000 --- a/routes/swaps.js +++ /dev/null @@ -1,587 +0,0 @@ -import { emit } from "$lib/sockets"; -import app from "$app"; -import config from "$config"; -import { auth, optionalAuth } from "$lib/passport"; -import store from "$lib/store"; -import axios from "axios"; -import crypto from "crypto"; -import fs from "fs"; -import getAccount from "../lib/account"; -import { Op, col } from "@sequelize/core"; - -const shallow = a => { - let b = {}; - for (x in a) { - if (a[x] instanceof Date || typeof a[x] !== "object") b[x] = a[x]; - } - return b; -}; - -const assets = { - b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23: "bitcoin", - "4eebe36eb0819e6daa5dd3c97733251ff4eb728c810d949365d6dacaad5ef6e8": "tether" -}; - -const timeout = 20000; - -const swap = async (user, { a1, a2, v1, v2 }) => { - if (!parseInt(v1) || v1 < 0 || !parseInt(v2) || v2 < 0) - throw new Error("Invalid amount"); - let rate = v2 / v1; - - const b = await lq.getBalance(); - - Object.keys(b).map(asset => { - assets[asset] = asset; - }); - - assets[config.liquid.btcasset] = "bitcoin"; - assets["ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2"] = - "tether"; - assets["a0c358a0f6947864af3a06f3f6a2aeb304df7fd95c922f2f22d7412399ce7691"] = - "adamcoin"; - - if (!assets[a1]) throw new Error("Asset not found on server"); - if (v1 > Math.round(b[assets[a1]] * SATS)) - throw new Error(`Insufficient server funds, ${v1} ${b[assets[a1]]}`); - - await db.transaction(async transaction => { - let a1acc = await getAccount(a1, user, transaction); - let a2acc = await getAccount(a2, user, transaction); - - if (v1 > a1acc.balance) - throw new Error(`Insufficient funds, ${v1} ${a1acc.balance}`); - - l( - `placing order ${v1} ${a1.substr(0, 6)} for ${v2} ${a2.substr(0, 6)}`, - user.username - ); - - await a1acc.decrement({ balance: v1 }, { transaction }); - await a1acc.reload({ transaction }); - - let payment = await db.Payment.create( - { - hash: "Trade Funding", - amount: -v1, - account_id: a1acc.id, - user_id: user.id, - currency: user.currency, - rate: store.rates[user.currency], - confirmed: true, - received: false, - network: "COINOS" - }, - { transaction } - ); - - emit(user.username, "payment", payment.get({ plain: true })); - emit(user.username, "account", a1acc.get({ plain: true })); - - const orders = await db.Order.findAll( - { - where: { - "$acc1.asset$": a2, - "$acc2.asset$": a1, - rate: { [Op.lte]: v1 / v2 }, - accepted: false - }, - order: [ - ["rate", "ASC"], - ["id", "ASC"] - ], - include: [ - { - model: db.User, - as: "user" - }, - { - model: db.Account, - as: "acc1" - }, - { - model: db.Account, - as: "acc2" - } - ] - }, - transaction - ); - - for (let i = 0; i < orders.length; i++) { - if (!v1) break; - let order = orders[i]; - if (order.v2 > v1) { - let rate = order.rate; - await order.decrement( - { - v2: v1 - }, - { transaction } - ); - await order.save({ transaction }); - await order.reload({ transaction }); - order.v1 = Math.round(order.v2 / rate); - await order.save({ transaction }); - - await order.acc1.increment({ balance: v2 }, { transaction }); - await order.acc1.reload({ transaction }); - - emit(order.user.username, "account", order.acc1.get({ plain: true })); - - payment = await db.Payment.create( - { - hash: "Trade Fill", - amount: v1, - account_id: a1acc.id, - user_id: user.id, - currency: user.currency, - rate: store.rates[user.currency], - confirmed: true, - received: true, - network: "COINOS" - }, - { transaction } - ); - - emit(user.username, "payment", payment.get({ plain: true })); - emit(user.username, "account", a1acc.get({ plain: true })); - - if (order.fee) { - const btc = await getAccount( - config.liquid.btcasset, - order.user, - transaction - ); - - payment = await db.Payment.create( - { - hash: "Swap Fee Refund", - amount: order.fee, - account_id: btc.id, - user_id: order.user_id, - currency: order.user.currency, - rate: store.rates[order.user.currency], - confirmed: true, - received: true, - network: "COINOS" - }, - { transaction } - ); - - await btc.increment({ balance: order.fee }, { transaction }); - await btc.reload({ transaction }); - - emit(order.user.username, "account", btc.get({ plain: true })); - } - - emit(order.user.username, "payment", payment.get({ plain: true })); - - order = order.get({ plain: true }); - order.a1 = order.acc1.asset; - order.a2 = order.acc2.asset; - order.rate = order.v2 / order.v1; - - broadcast("order", shallow(order)); - v1 = 0; - } else { - v1 -= order.v2; - v2 = Math.round(v1 * rate); - - order.accepted = true; - order.completedAt = new Date(); - - await order.save({ transaction }); - - let payment = await db.Payment.create( - { - hash: "Trade Fill", - amount: order.v1, - account_id: a2acc.id, - user_id: user.id, - currency: user.currency, - rate: store.rates[user.currency], - confirmed: true, - received: true, - network: "COINOS" - }, - { transaction } - ); - - await a2acc.increment({ balance: order.v1 }, { transaction }); - await a2acc.reload({ transaction }); - - emit(user.username, "payment", payment.get({ plain: true })); - emit(user.username, "account", a2acc.get({ plain: true })); - - payment = await db.Payment.create( - { - hash: "Trade Fill", - amount: order.v2, - account_id: order.a2_id, - user_id: order.user_id, - currency: order.user.currency, - rate: store.rates[order.user.currency], - confirmed: true, - received: true, - network: "COINOS" - }, - { transaction } - ); - - emit(order.user.username, "payment", payment.get({ plain: true })); - await order.acc2.increment({ balance: order.v2 }, { transaction }); - await order.acc2.reload({ transaction }); - - order = order.get({ plain: true }); - order.a1 = order.acc1.asset; - order.a2 = order.acc2.asset; - order.rate = order.v2 / order.v1; - - broadcast("order", shallow(order)); - } - } - - let order; - if (v1) { - order = await db.Order.create( - { - v1, - v2, - user_id: user.id, - a1_id: a1acc.id, - a2_id: a2acc.id - }, - { transaction } - ); - - order = order.get({ plain: true }); - order.rate = v2 / v1; - order.a1 = a1; - order.a2 = a2; - - broadcast("order", order); - } - }); -}; - -const cancel = async (user, id) => { - await db.transaction(async transaction => { - let order = await db.Order.findOne( - { - where: { - id, - user_id: user.id - }, - include: { - model: db.Account, - as: "acc1" - } - }, - - { transaction } - ); - - let { acc1: account, v1, v2 } = order; - await account.increment({ balance: v1 }, { transaction }); - await account.reload({ transaction }); - - let rate = v2 / v1; - let payment = await db.Payment.create( - { - hash: "Trade Cancelled", - amount: v1, - account_id: account.id, - user_id: user.id, - currency: user.currency, - rate: store.rates[user.currency], - confirmed: true, - received: true, - network: "COINOS" - }, - { transaction } - ); - - emit(user.username, "account", account.get({ plain: true })); - emit(user.username, "payment", payment.get({ plain: true })); - - if (order.fee) { - const btc = await getAccount(config.liquid.btcasset, user, transaction); - - payment = await db.Payment.create( - { - hash: "Swap Fee Refund", - amount: order.fee, - account_id: btc.id, - user_id: user.id, - currency: user.currency, - rate: store.rates[user.currency], - confirmed: true, - received: true, - network: "COINOS" - }, - { transaction } - ); - - await btc.increment({ balance: order.fee }, { transaction }); - await btc.reload({ transaction }); - - emit(user.username, "account", btc.get({ plain: true })); - emit(user.username, "payment", payment.get({ plain: true })); - } - - broadcast("removeOrder", id); - await order.destroy({ transaction }); - }); -}; - -app.delete("/order/:id", auth, async (req, res) => { - const { id } = req.params; - const { user } = req; - - await cancel(user, id); - res.send({}); -}); - -app.post("/orders", auth, async (req, res) => { - return res.code(500).send("Trading temporarily disabled"); - const { user } = req; - try { - await swap(user, req.body); - res.send({}); - } catch (e) { - err(req.user.username, e.message); - res.code(500).send(e.message); - } -}); - -app.get("/orders", optionalAuth, async (req, res) => { - try { - let orders = await db.Order.findAll({ - attributes: { - include: [ - [col("acc1.asset"), "a1"], - [col("acc2.asset"), "a2"] - ] - }, - include: [ - { - model: db.Account, - as: "acc1", - attributes: [] - }, - { - model: db.Account, - as: "acc2", - attributes: [] - } - ] - }); - - res.send( - orders.map(order => { - if (order.user_id !== req.user.id) order.user_id = null; - return order; - }) - ); - } catch (e) { - res.code(500).send(e.message); - } -}); - -if (config.maker) { - debug("setup maker account..."); - config.maker.map( - ({ amount, c1, c2, currency, askMultiplier, bidMultiplier }) => { - setInterval(async () => { - if (!rates) return; - - const user = await db.User.findOne({ - where: { - username: "maker" - } - }); - - if (!user) { - debug("maker user not defined in database"); - } else { - debug("maker: " + JSON.stringify(user)); - await db.transaction(async transaction => { - let order = await db.Order.findOne( - { - where: { - user_id: user.id, - "$acc1.asset$": c1, - "$acc2.asset$": c2, - accepted: false - }, - include: [ - { - model: db.Account, - as: "acc1" - }, - { - model: db.Account, - as: "acc2" - } - ] - }, - { transaction } - ); - - let params = { - a1: c1, - a2: c2, - v1: amount * SATS, - v2: Math.round( - amount * - SATS * - (((app.get("ask") * store.rates[currency]) / - store.rates["USD"]) * - askMultiplier) - ) - }; - - if (order) { - let bestBid = await db.Order.findOne( - { - where: { - "$acc1.asset$": c2, - "$acc2.asset$": c1, - accepted: false, - id: { - [Op.ne]: order.id - } - }, - include: [ - { - model: db.Account, - as: "acc1" - }, - { - model: db.Account, - as: "acc2" - } - ], - order: [["rate", "DESC"]], - limit: 1 - }, - { transaction } - ); - if ( - !bestBid || - (params.v2 / params.v1 > bestBid.rate && - order.acc2.balance >= params.v2) - ) { - order.v1 = params.v1; - order.v2 = params.v2; - await order.save(); - order = order.get({ plain: true }); - order.a1 = params.a1; - order.a2 = params.a2; - order.rate = order.v2 / order.v1; - - broadcast("order", shallow(order)); - } - } else { - try { - await swap(user, params); - } catch (e) { - warn("Failed to make ask", e.message); - } - } - - order = await db.Order.findOne( - { - where: { - user_id: user.id, - "$acc1.asset$": c2, - "$acc2.asset$": c1, - accepted: false - }, - include: [ - { - model: db.Account, - as: "acc1" - }, - { - model: db.Account, - as: "acc2" - } - ] - }, - { transaction } - ); - - params = { - a1: c2, - a2: c1, - v1: Math.round( - amount * - SATS * - ((app.get("bid") * store.rates[currency]) / - store.rates["USD"]) * - bidMultiplier - ), - v2: amount * SATS - }; - - if (order) { - let bestAsk = await db.Order.findOne( - { - where: { - "$acc1.asset$": c1, - "$acc2.asset$": c2, - accepted: false, - id: { - [Op.ne]: order.id - } - }, - include: [ - { - model: db.Account, - as: "acc1" - }, - { - model: db.Account, - as: "acc2" - } - ], - order: [["rate", "ASC"]], - limit: 1 - }, - transaction - ); - - if (bestAsk) - if ( - !bestAsk || - (params.v1 / params.v2 < bestAsk.rate && - order.acc1.balance >= params.v1) - ) { - order.v1 = params.v1; - order.v2 = params.v2; - await order.save(); - order = order.get({ plain: true }); - order.a1 = params.a1; - order.a2 = params.a2; - order.rate = order.v2 / order.v1; - - broadcast("order", shallow(order)); - } else { - broadcast("removeOrder", order.id); - await order.destroy({ transaction }); - } - } else { - try { - await swap(user, params); - } catch (e) { - warn("Failed to make bid", e.message); - } - } - }); - } - }, 5000); - } - ); -} diff --git a/routes/tickets.js b/routes/tickets.js deleted file mode 100644 index 7ca56740..00000000 --- a/routes/tickets.js +++ /dev/null @@ -1,22 +0,0 @@ -import app from "$app"; -import redis from "$lib/redis"; -import { auth } from "$lib/passport"; - -app.get("/ticket", async (req, res) => { - res.send({ ticket: await redis.lLen("tickets") }); -}); - -app.get("/tickets", async (req, res) => { - res.send(await redis.lRange("tickets", 0, -1)); -}); - -app.post("/ticket", auth, async (req, res) => { - try { - let { asset } = req.body; - if (!asset || asset.length !== 64) throw new Error("invalid asset"); - - res.send(await redis.rPush("tickets", req.body.asset)); - } catch (e) { - res.code(500).send(e.message); - } -}); diff --git a/routes/users.js b/routes/users.js index 4e6b8a63..4a01cc1e 100644 --- a/routes/users.js +++ b/routes/users.js @@ -7,19 +7,11 @@ import { getUser, nada, uniq, wait } from "$lib/utils"; import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { authenticator } from "otplib"; -import Sequelize from "@sequelize/core"; -import { fromBase58, fromPrivateKey } from "bip32"; -import { Mutex } from "async-mutex"; -import bip32 from "bip32"; import whitelist from "$lib/whitelist"; import { l, err, warn } from "$lib/logging"; -import db from "$db"; -import bc from "$lib/bitcoin"; -import lq from "$lib/liquid"; import { emit } from "$lib/sockets"; import register from "$lib/register"; import { requirePin } from "$lib/utils"; -import { coinos, q } from "$lib/nostr"; import got from "got"; import { bech32 } from "bech32"; @@ -30,28 +22,7 @@ const pick = (O, ...K) => K.reduce((o, k) => ((o[k] = O[k]), o), {}); app.get("/me", auth, async (req, res) => { try { let user = req.user.get({ plain: true }); - let payments = await req.user.getPayments({ - where: { - account_id: user.account_id - }, - order: [["id", "DESC"]], - limit: 12, - include: [ - { - model: db.Account, - as: "account" - }, - { - model: db.Payment, - as: "fee_payment" - } - ] - }); - - user.accounts = await req.user.getAccounts(); - user.payments = payments; user.haspin = !!user.pin; - res.send(pick(user, ...whitelist)); } catch (e) { console.log("problem fetching user", e); @@ -59,53 +30,37 @@ app.get("/me", auth, async (req, res) => { } }); -app.get("/users/:username", async (req, res) => { +app.get("/users/:key", async (req, res) => { try { - let { username } = req.params; - - if (username.startsWith("npub")) { - username = Buffer.from(fromWords(decode(username).words)).toString("hex"); + if (key.startsWith("npub")) { + key = Buffer.from(fromWords(decode(key).words)).toString("hex"); } - let user = await db.User.findOne({ - attributes: [ - "username", - "banner", - "profile", - "address", - "currency", - "pubkey", - "display", - "prompt", - "uuid" - ], - where: { - [Sequelize.Op.or]: [ - Sequelize.where( - Sequelize.fn("lower", Sequelize.col("username")), - username.toLowerCase() - ), - { pubkey: username } - ] - } - }); - - if (user) user = user.get({ plain: true }); - else user = JSON.parse(await redis.get(`user:${username}`)); + user = JSON.parse(await redis.get(`user:${key}`)); - if (!user && username.length === 64) { - let newuser = { - username: username.substr(0, 6), - pubkey: username, + if (!user && key.length === 64) { + user = { + username: key.substr(0, 6), + pubkey: key, anon: true }; - - user = JSON.parse(await redis.get(`user:${newuser.pubkey}`)) || newuser; } if (!user) return res.code(500).send("User not found"); - res.send(user); + let whitelist = [ + "username", + "banner", + "profile", + "address", + "currency", + "pubkey", + "display", + "prompt", + "uuid" + ]; + + res.send(pick(user, ...whitelist)); } catch (e) { console.log(e); res.code(500).send(e.message); @@ -402,8 +357,6 @@ let login = async (req, res) => { }; app.post("/login", login); -app.post("/taboggan", login); -app.post("/doggin", login); app.post("/logout", optionalAuth, async (req, res) => { let { subscription } = req.body; @@ -428,100 +381,6 @@ app.post("/logout", optionalAuth, async (req, res) => { res.send({}); }); -app.post("/account", auth, async (req, res) => { - const { user } = req; - const { - id, - name, - ticker, - precision, - domain, - seed, - pubkey, - privkey, - path, - hide, - index - } = req.body; - - try { - await db.transaction(async transaction => { - let account = await db.Account.findOne({ - where: { id, user_id: user.id }, - lock: transaction.LOCK.UPDATE, - transaction - }); - - let params = { - name, - ticker, - precision, - domain, - seed, - pubkey, - path, - hide, - index, - privkey - }; - - if (!account || (account.pubkey && !pubkey)) delete params.pubkey; - - await db.Account.update(params, { - where: { id, user_id: user.id }, - transaction - }); - - await account.reload({ transaction }); - emit(user.username, "account", account); - res.send({}); - }); - } catch (e) { - err("problem updating account", e.message); - return res.code(500).send("There was a problem updating the account"); - } -}); - -app.post("/shiftAccount", auth, async (req, res) => { - let { user } = req; - let { id } = req.body; - - try { - const account = await db.Account.findOne({ - where: { id } - }); - - if (account.user_id !== user.id) - return res.code(500).send("Failed to open wallet"); - - user.account_id = account.id; - await user.save(); - let payments = await db.Payment.findAll({ - where: { - account_id: id - }, - order: [["id", "DESC"]], - limit: 12, - include: { - model: db.Account, - as: "account" - } - }); - - user = user.get({ plain: true }); - user.payments = payments; - user.account = account.get({ plain: true }); - - emit(user.username, "account", user.account); - emit(user.username, "user", user); - - res.send(user); - } catch (e) { - err("problem switching account", e.message); - return res.code(500).send("There was a problem switching accounts"); - } -}); - app.get("/vapidPublicKey", function(req, res) { res.send(config.vapid.publicKey); }); @@ -548,36 +407,6 @@ app.post("/password", auth, async function(req, res) { res.send(await bcrypt.compare(password, user.password)); }); -app.get("/isInternal", auth, async function(req, res) { - let { user } = req; - let { address } = req.query; - if (!address) throw new Error("Address not provided"); - - let invoice = await db.Invoice.findOne({ - where: { address }, - include: { - attributes: ["username"], - model: db.User, - as: "user" - } - }); - - if (invoice) { - let info; - try { - info = await bc.getAddressInfo(address); - } catch (e) { - info = await lq.getAddressInfo(address); - } - if (info.ismine) { - emit(user.username, "to", invoice.user); - return res.send(true); - } - } - - res.send(false); -}); - app.get("/invoices", auth, async function(req, res) { let invoices = await db.Invoice.findAll({ where: { user_id: req.user.id } @@ -585,22 +414,6 @@ app.get("/invoices", auth, async function(req, res) { res.send(invoices); }); -app.post("/signMessage", auth, async function(req, res) { - let { address, message } = req.body; - - let invoices = await db.Invoice.findAll({ - where: { user_id: req.user.id } - }); - - if (invoices.find(i => i.address === address)) { - if (config.bitcoin.walletpass) - await bc.walletPassphrase(config.bitcoin.walletpass, 300); - return res.send(await bc.signMessage(address, message)); - } - - res.code(500).send("Address not found for user"); -}); - app.post("/otpsecret", auth, async function(req, res) { try { await requirePin(req); @@ -652,105 +465,3 @@ app.post("/checkPassword", auth, async function(req, res) { res.code(500).send("Invalid password"); } }); - -app.get("/:pubkey/follows", async (req, res) => { - try { - let { pubkey } = req.params; - let { tagsonly } = req.query; - - let sub = `${pubkey}:follows`; - q( - sub, - { - limit: 1, - kinds: [3], - authors: [pubkey] - }, - { timeout: 60000, eager: 60000 } - ).catch(nada); - - let tags = JSON.parse(await redis.get(`${pubkey}:follows`)) || []; - if (tagsonly) return res.send(tags); - - let follows = []; - for (let f of tags) { - let [_, pubkey] = f; - - q(`${pubkey}:profile:f1`, { - limit: 1, - kinds: [0], - authors: [pubkey] - }).catch(nada); - - let user = JSON.parse(await redis.get(`user:${pubkey}`)); - - if (!user) - user = { - username: pubkey.substr(0, 6), - pubkey, - anon: true - }; - - follows.push(user); - } - - follows = uniq(follows, e => e.pubkey); - follows.sort((a, b) => a.username.localeCompare(b.username)); - - res.send(follows); - } catch (e) { - console.log(e); - res.code(500).send(e && e.message); - } -}); - -app.get("/:pubkey/followers", async (req, res) => { - try { - let { pubkey } = req.params; - - let pubkeys = [ - ...new Set([ - ...(await got(`${config.nostr}/followers?pubkey=${pubkey}`).json()), - ...(await redis.sMembers(`${pubkey}:followers`)) - ]) - ]; - - let followers = []; - - q( - `${pubkey}:followers`, - { kinds: [3], "#p": [pubkey] }, - { timeout: 60000, eager: 60000 } - ).catch(nada); - - for (let pubkey of pubkeys) { - let user = JSON.parse(await redis.get(`user:${pubkey}`)); - if (!user || !user.updated) { - q(`${pubkey}:profile:f2`, { - limit: 1, - kinds: [0], - authors: [pubkey] - }).catch(nada); - - user = JSON.parse(await redis.get(`user:${pubkey}`)); - } - - if (!user) - user = { - username: pubkey.substr(0, 6), - pubkey, - anon: true - }; - - followers.push(user); - } - - followers = uniq(followers, e => e.pubkey); - followers.sort((a, b) => a.username.localeCompare(b.username)); - - res.send(followers); - } catch (e) { - console.log(e); - res.code(500).send(e && e.message); - } -}); |
