aboutsummaryrefslogtreecommitdiffstats
path: root/routes
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2023-02-06 16:48:13 -0800
committerAdam Soltys <[email protected]>2023-02-06 16:48:13 -0800
commit9c667055c6367e1b2e239686e72f52995679a47e (patch)
tree0b1d4aee51ec2d4e92d08a9d1404ba4e2ecd462a /routes
parent15294844dbc8e73b4d1e92008a7eebc6b86b9ec5 (diff)
parent6f5602d112b68adb7452abf3047f20c32e7725b6 (diff)
downloadcoinos-server-9c667055c6367e1b2e239686e72f52995679a47e.tar.gz
coinos-server-9c667055c6367e1b2e239686e72f52995679a47e.zip
Merge branch 'master' into test
Diffstat (limited to 'routes')
-rw-r--r--routes/admin.js566
-rw-r--r--routes/assets.js536
-rw-r--r--routes/bitcoin/broadcast.js56
-rw-r--r--routes/bitcoin/conversionFee.js12
-rw-r--r--routes/bitcoin/fee.js73
-rw-r--r--routes/bitcoin/generate.js5
-rw-r--r--routes/bitcoin/index.js13
-rw-r--r--routes/bitcoin/receive.js281
-rw-r--r--routes/bitcoin/send.js193
-rw-r--r--routes/bitcoin/sweep.js87
-rw-r--r--routes/email.js24
-rw-r--r--routes/funding.js96
-rw-r--r--routes/info.js90
-rw-r--r--routes/invoices.js226
-rw-r--r--routes/lightning/channel.js29
-rw-r--r--routes/lightning/channelRequest.js24
-rw-r--r--routes/lightning/conversionFee.js12
-rw-r--r--routes/lightning/index.js13
-rw-r--r--routes/lightning/invoice.js25
-rw-r--r--routes/lightning/parse.js19
-rw-r--r--routes/lightning/query.js20
-rw-r--r--routes/lightning/receive.js162
-rw-r--r--routes/lightning/send.js17
-rw-r--r--routes/liquid/broadcast.js58
-rw-r--r--routes/liquid/conversionFee.js12
-rw-r--r--routes/liquid/fee.js89
-rw-r--r--routes/liquid/generate.js5
-rw-r--r--routes/liquid/index.js11
-rw-r--r--routes/liquid/receive.js406
-rw-r--r--routes/liquid/send.js276
-rw-r--r--routes/lnurl.js610
-rw-r--r--routes/locations.js24
-rw-r--r--routes/nostr.js157
-rw-r--r--routes/payments.js351
-rw-r--r--routes/rates.js11
-rw-r--r--routes/referrals.js272
-rw-r--r--routes/requests.js158
-rw-r--r--routes/send.js14
-rw-r--r--routes/swaps.js587
-rw-r--r--routes/tickets.js22
-rw-r--r--routes/users.js883
41 files changed, 841 insertions, 5684 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 c52592ae..00000000
--- a/routes/bitcoin/send.js
+++ /dev/null
@@ -1,193 +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);
- console.log(e);
- 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/email.js b/routes/email.js
new file mode 100644
index 00000000..529307e6
--- /dev/null
+++ b/routes/email.js
@@ -0,0 +1,24 @@
+import config from "$config";
+
+export default {
+ async send({ body }, res) {
+ try {
+ let postmark = await import("postmark");
+ let client = new postmark.ServerClient(config.postmark);
+
+ await client.sendEmail({
+ Subject: body.subject || "Email Signup",
+ HtmlBody: JSON.stringify(body),
+ TextBody: JSON.stringify(body),
+ MessageStream: "outbound"
+ });
+
+ res.send({ ok: true });
+ } catch (e) {
+ console.log("problem sending email", e);
+ res.code(500).send(e.message);
+ }
+ }
+};
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}`,
- };
-
- 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/info.js b/routes/info.js
index eb42ca35..017e473a 100644
--- a/routes/info.js
+++ b/routes/info.js
@@ -1,90 +1,32 @@
-import app from "$app";
-import db from "$db";
-import config from "$config";
-import store from "$lib/store";
-
-import bc from "$lib/bitcoin";
-import lq from "$lib/liquid";
+import db from "$lib/db";
import ln from "$lib/ln";
-import sequelize from "@sequelize/core";
-import { SATS } from "$lib/utils";
-
-const { Op } = sequelize;
-
-/**
- * @api {get} /info Request server information
- * @apiName GetInfo
- * @apiGroup Info
- *
- * @apiSuccess {Object} fx Exchange rates relative to USD - fx.CUR is the value of one CUR in USD.
- * @apiSuccess {String[]} networks Array of supported networks; possible values are "bitcoin", "liquid" and "lightning".
- * @apiSuccess {String} clientVersion the current git commit of the ui
- */
-app.get("/info", async (req, res, next) => {
- const { clientVersion } = config;
-
- const info = {
- fx: store.fx,
- nodes: store.networks,
- clientVersion
- };
-
- res.send(info);
-});
+export default {
+ async balances(req, res) {
+ let lnchannel;
+ let lnwallet;
-app.get("/balances", async (req, res, next) => {
- try {
- const accounts = await db.Account.findAll({
- where: {
- pubkey: null
- },
- attributes: [
- "asset",
- [sequelize.fn("sum", sequelize.col("balance")), "total"]
- ],
- group: ["asset"]
+ const stream = db.scanStream({
+ match: "balance:*",
+ count: 100
});
- let lnchannel;
- let lnwallet;
+ let b = 0;
+ for await (let keys of stream) {
+ for (let k of keys) b += parseInt(await db.get(k));
+ }
+ console.log(b);
const funds = await ln.listfunds();
lnchannel = parseInt(funds.channels.reduce((a, b) => a + b.channel_sat, 0));
lnwallet = parseInt(funds.outputs.reduce((a, b) => a + b.value, 0));
- let bitcoin, bitcoind;
- if (config.bitcoin) {
- bitcoin = parseInt((await bc.getBalance()) * SATS);
- bitcoind = await bc.getNetworkInfo();
- }
-
- let assets, liquid, elementsd;
- if (config.liquid) {
- assets = await lq.getBalance();
- liquid = parseInt(assets.bitcoin * SATS);
- elementsd = await lq.getNetworkInfo();
- }
-
const info = {
- bitcoind,
- elementsd,
- accounts,
- assets,
- bitcoin,
- liquid,
lnchannel,
- lnwallet
+ lnwallet,
+ total: lnchannel + lnwallet
};
- info.total =
- parseInt(bitcoin) +
- parseInt(liquid) +
- parseInt(lnchannel) +
- parseInt(lnwallet);
-
res.send(info);
- } catch (e) {
- console.log("problem getting balances", e);
}
-});
+};
diff --git a/routes/invoices.js b/routes/invoices.js
index 7fa82afb..d856ef6b 100644
--- a/routes/invoices.js
+++ b/routes/invoices.js
@@ -1,209 +1,39 @@
-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 { emit } from "$lib/sockets";
+import { g, s } from "$lib/db";
+import { generate } from "$lib/invoices";
+import got from "got";
+import config from "$config";
-app.get("/invoice", async (req, res, next) => {
- try {
- const invoice = await db.Invoice.findOne({
- where: {
- uuid: req.query.uuid
- },
- include: [
- {
- model: db.User,
- as: "user",
- attributes: ["username", "currency", "profile"]
- },
- {
- model: db.Request,
- as: "request"
- }
- ]
- });
-
- res.send(invoice);
- } catch (e) {
- err("invoice not found", e);
- res.code(500).send("Invoice not found");
- }
-});
-
-app.get("/invoice/:text", async (req, res, next) => {
- try {
- let { text } = req.params;
- let where =
- text.split("-").length > 4
- ? { uuid: text }
- : text.startsWith("ln")
- ? { text }
- : {
- [Op.or]: [{ unconfidential: text }, { address: text }]
- };
-
- const invoice = await db.Invoice.findOne({
- where,
- include: {
- model: db.User,
- as: "user",
- attributes: ["username", "currency", "profile"]
- }
- });
-
- res.send(invoice);
- } catch (e) {
- err("invoice not found", e);
- res.code(500).send("Invoice not found");
- }
-});
-
-app.post("/invoice", optionalAuth, async (req, res, next) => {
- try {
- let { type = "bech32", liquidAddress, id, invoice, user, tx } = req.body;
- let {
- blindkey,
- currency,
- tip,
- amount,
- rate,
- network,
- request_id
- } = invoice;
- let address, unconfidential, text;
-
- if (amount < 0) throw new Error("amount out of range");
- if (tip > 5 * amount || tip < 0) throw new Error("tip amount out of range");
-
- if (!user) ({ user } = req);
- else {
- user = await db.User.findOne({
- where: {
- username: user.username
- },
- include: {
- model: db.Account,
- as: "account"
- }
- });
- }
- if (!user) throw new Error("user not provided");
- if (!currency) currency = user.currency;
- if (!rate) rate = store.rates[currency];
- if (amount < 0) throw new Error("invalid amount");
+export default {
+ async get({ params: { hash } }, res) {
+ let invoice = await g(`invoice:${hash}`);
- if (user.account.pubkey) {
- let { address, confidentialAddress } = await deriveAddress(
- user.account,
- type
- );
- if (confidentialAddress) {
- address = confidentialAddress;
- unconfidential = address;
- } else {
- address = address;
- }
- } else if (network !== "lightning") {
- address = await { bitcoin: bc, liquid: lq }[network].getNewAddress();
- if (network === "liquid")
- ({ unconfidential } = await lq.getAddressInfo(address));
- }
-
- invoice = {
- ...invoice,
- account_id: user.account.id,
- address,
- amount,
- currency,
- rate,
- tip,
- unconfidential,
- user_id: user.id
- };
-
- if (network === "lightning") {
- invoice.text = await derivePayRequest(invoice);
+ if (invoice) {
+ invoice.user = await g(`user:${invoice.uid}`);
} else {
- invoice.text = bip21(invoice, user.account);
+ invoice = await got(`${config.classic}/invoice/${hash}`).json();
+ invoice.id = invoice.uuid;
+ invoice.classic = true;
+ invoice.user.id = invoice.user.uuid;
+ invoice.user.username += "@classic";
}
- if (liquidAddress) {
- if (network === "lightning") {
- l("conversion request for", liquidAddress, invoice.text);
- store.convert[invoice.text] = { address: liquidAddress, tx };
- } else if (network === "liquid") {
- l("conversion request for", liquidAddress, invoice.unconfidential);
- store.convert[invoice.unconfidential] = { address: liquidAddress, tx };
- } else {
- l("conversion request for", liquidAddress, invoice.address);
- store.convert[invoice.address] = { address: liquidAddress, tx };
- }
- }
-
- l(
- "creating invoice",
- user.username,
- invoice.network,
- invoice.amount,
- invoice.tip,
- invoice.currency,
- invoice.text && `${invoice.text.substr(0, 8)}..${invoice.text.substr(-6)}`
- );
-
- if (!invoice.tip) invoice.tip = 0;
-
- invoice = await db.Invoice.create(invoice);
- store.addresses[invoice.address] = user.username;
- if (invoice.unconfidential) {
- store.addresses[invoice.unconfidential] = user.username;
- if (blindkey) await lq.importBlindingKey(invoice.address, blindkey);
- }
+ res.send(invoice);
+ },
- if (request_id) {
- let request = await db.Request.findOne({
- where: { id: request_id },
+ async create({ body: { invoice, user }, user: sender }, res) {
+ res.send(await generate({ invoice, user, sender }));
+ },
- include: {
- model: db.User,
- as: "requester",
- attributes: ["username"]
+ async classic({ params: { username } }, res) {
+ let invoice = await got
+ .post(`${config.classic}/invoice`, {
+ json: {
+ invoice: { amount: 0, network: "lightning" },
+ user: { username }
}
- });
-
- if (request) {
- let existing = request.invoice_id;
- request.invoice_id = invoice.id;
- await request.save();
-
- if (!existing)
- emit(request.requester.username, "invoice", {
- amount: invoice.amount,
- currency: invoice.currency,
- rate: invoice.rate,
- request_id,
- uuid: invoice.uuid,
- user: { username: user.username, profile: user.profile }
- });
- }
- }
+ })
+ .json();
res.send(invoice);
- } catch (e) {
- err(e.message, e.stack);
- res.code(500).send(`Problem during invoice creation: ${e.message}`);
- }
-});
-
-app.post(
- "/:username/:network/invoice",
- optionalAuth,
- async (req, res, next) => {
- let { network, username } = req.params;
}
-);
+};
diff --git a/routes/lightning/channel.js b/routes/lightning/channel.js
deleted file mode 100644
index d5d5b7b2..00000000
--- a/routes/lightning/channel.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import axios from "axios";
-import ln from "$lib/ln";
-
-export default async (req, res) => {
- const { params } = req.body;
- const [public_key, socket] = params.uri.split("@");
-
- l("connecting to peer", req.user.username, public_key, socket);
- let result;
-
- const { callback, k1 } = params;
- let remoteid;
- try {
- remoteid = (await ln.getinfo()).id;
- } catch (e) {
- err("problem getting lightning node info", e.message);
- return res.code(500).send(e.message);
- }
-
- let url = `${callback}?k1=${k1}&remoteid=${remoteid}&private=0`;
-
- try {
- let response = await axios.get(url);
- res.send(response.data);
- } catch (e) {
- err("problem sending channel request", e.message);
- return res.code(500).send({ status: "ERROR", reason: e.message });
- }
-};
diff --git a/routes/lightning/channelRequest.js b/routes/lightning/channelRequest.js
deleted file mode 100644
index 74e4cbba..00000000
--- a/routes/lightning/channelRequest.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import { lnurlServer } from "$routes/lnurl";
-import { err } from "$lib/logging";
-
-export const channelRequest = async (req, res) => {
- let { localAmt, pushAmt } = req.body;
-
- pushAmt = 0;
-
- try {
- if (localAmt < 20000)
- throw new Error("amount must be greater than 20000 SAT");
- if (localAmt > 1000000) throw new Error("amount must be <= 1000000");
-
- const result = await lnurlServer.generateNewUrl("channelRequest", {
- localAmt,
- pushAmt
- });
-
- res.send(result);
- } catch (e) {
- err("problem generating channel request", e.message);
- res.code(500).send(e.message);
- }
-};
diff --git a/routes/lightning/conversionFee.js b/routes/lightning/conversionFee.js
deleted file mode 100644
index f6cf434c..00000000
--- a/routes/lightning/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' lightning BTC.
- */
-export function computeConversionFee(amount) {
- return Math.floor(amount * conversionFeeMultiplier);
-}
diff --git a/routes/lightning/index.js b/routes/lightning/index.js
deleted file mode 100644
index b4611ef5..00000000
--- a/routes/lightning/index.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import channel from "./channel";
-import invoice from "./invoice";
-import query from "./query";
-import send from "./send";
-import parse from "./parse";
-
-export default {
- channel,
- invoice,
- query,
- send,
- parse
-};
diff --git a/routes/lightning/invoice.js b/routes/lightning/invoice.js
deleted file mode 100644
index ae974120..00000000
--- a/routes/lightning/invoice.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import config from "$config";
-import { err } from "$lib/logging";
-import ln from "$lib/ln";
-import { v4 } from "uuid";
-
-export default async (req, res) => {
- let { amount, memo, tip } = req.body;
- if (!tip) tip = 0;
- let value = amount + tip;
-
- try {
- if (!memo) memo = "coinos";
- const invoice = await ln.invoice(
- value ? `${value}sat` : "any",
- v4(),
- memo,
- 360
- );
- res.send({ text: invoice.bolt11 });
- } catch (e) {
- console.log(e);
- err("problem creating invoice", e.message);
- res.code(500).send(e.message);
- }
-};
diff --git a/routes/lightning/parse.js b/routes/lightning/parse.js
deleted file mode 100644
index 1b77019a..00000000
--- a/routes/lightning/parse.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import ln from "$lib/ln";
-import store from "$lib/store";
-
-export default async (req, res) => {
- let { payreq } = req.body;
- let hour = 1000 * 60 * 60;
- let { last } = store.nodes;
- let { nodes } = store;
-
- if (!last || last > Date.now() - hour) ({ nodes } = await ln.listnodes());
- store.nodes = nodes;
-
- let twoWeeksAgo = new Date(new Date().setDate(new Date().getDate() - 14));
- let { msatoshi, payee } = await ln.decodepay(payreq);
- let node = nodes.find((n) => n.nodeid === payee);
- let alias = node ? node.alias : payee.substr(0, 12);
-
- res.send({ alias, amount: Math.round(msatoshi / 1000) });
-};
diff --git a/routes/lightning/query.js b/routes/lightning/query.js
deleted file mode 100644
index 1c217a08..00000000
--- a/routes/lightning/query.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import db from "$db";
-import { emit } from "$lib/sockets";
-
-export default async (req, res) => {
- const { payreq } = req.body;
-
- const invoice = await db.Invoice.findOne({
- include: {
- attributes: ["username"],
- model: db.User,
- as: "user"
- },
- where: {
- text: payreq
- }
- });
-
- if (invoice) emit(req.user.username, "to", invoice.user);
- res.send({});
-};
diff --git a/routes/lightning/receive.js b/routes/lightning/receive.js
deleted file mode 100644
index ef973acc..00000000
--- a/routes/lightning/receive.js
+++ /dev/null
@@ -1,162 +0,0 @@
-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";
-
-const handlePayment = async msg => {
- try {
- l("incoming lightning payment", msg.received);
-
- if (!msg.secret) return;
- let account, total, user;
-
- const invoice = await db.Invoice.findOne({
- where: {
- text: msg.request
- }
- });
-
- if (!invoice)
- return warn("received lightning with no invoice", msg.request);
-
- await db.transaction(async transaction => {
- const { text: hash, currency, memo, rate, tip, user_id } = invoice;
- const amount = parseInt(msg.received) - tip;
- if (amount < 0) throw new Error("amount out of range");
-
- account = await db.Account.findOne({
- where: {
- user_id,
- asset: config.liquid.btcasset,
- pubkey: null
- },
- include: {
- model: db.User,
- as: "user"
- },
- lock: transaction.LOCK.UPDATE,
- transaction
- });
-
- ({ user } = account);
-
- let preimage = msg.secret;
-
- let payment = await db.Payment.create(
- {
- account_id: account.id,
- user_id,
- hash,
- memo,
- amount,
- currency,
- preimage,
- rate,
- received: true,
- confirmed: true,
- network: "lightning",
- tip,
- invoice_id: invoice.id
- },
- { transaction }
- );
-
- total = amount + tip;
-
- invoice.status = "paid";
- await invoice.save({ transaction });
-
- await invoice.increment({ received: total }, { transaction });
- await account.increment({ balance: total }, { transaction });
- // get the # of fee credits you would need to pay off this amount of bitcoin
- await account.increment(
- { lightning_credits: computeConversionFee(total) },
- { transaction }
- );
- await account.reload({ transaction });
- await invoice.reload({ transaction });
- await payment.save({ transaction });
- store.payments.push(msg.request);
-
- payment = payment.get({ plain: true });
- payment.account = account.get({ plain: true });
- payment.invoice = invoice.get({ plain: true });
-
- callWebhook(invoice, payment);
-
- emit(user.username, "payment", payment);
- emit(user.username, "account", payment.account);
- notify(user, `Received ${total} SAT`);
-
- l(
- "lightning payment received",
- user.username,
- payment.amount,
- 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);
- }
-};
-
-const poll = async ln => {
- try {
- const wait = async i => {
- const {
- bolt11: request,
- pay_index,
- status,
- msatoshi_received,
- payment_preimage: secret
- } = await ln.waitanyinvoice(i);
-
- let settled = status === "paid";
- let received = parseInt(msatoshi_received / 1000);
-
- await handlePayment({
- request,
- received,
- secret
- });
- await wait(pay_index);
- };
-
- const { invoices } = await ln.listinvoices();
- await wait(Math.max(...invoices.map(i => i.pay_index).filter(n => n)));
- } catch (e) {
- err("problem waiting for c-lightning invoice", e.message);
- }
-};
-
-poll(ln);
diff --git a/routes/lightning/send.js b/routes/lightning/send.js
deleted file mode 100644
index b41d5ed0..00000000
--- a/routes/lightning/send.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import { err } from "$lib/logging";
-import send from "$lib/send";
-import { requirePin } from "$lib/utils";
-
-export default async (req, res) => {
- let { amount, route, memo, payreq } = req.body;
- let { user } = req;
-
- try {
- await requirePin(req);
- res.send(await send(amount, memo, payreq, user));
- } catch (e) {
- console.log(e);
- err("problem sending lightning payment", user.username, payreq, e.message);
- res.code(500).send(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..63488033 100644
--- a/routes/lnurl.js
+++ b/routes/lnurl.js
@@ -1,578 +1,66 @@
-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";
+import { g, s } from "$lib/db";
+import { l } from "$lib/logging";
+import { fail } from "$lib/utils";
+import { v4 } from "uuid";
+import got from "got";
+import { generate } from "$lib/invoices";
+import { bech32 } from "bech32";
-import ln from "$lib/ln";
-import persist from "$lib/persist";
-import { emit } from "$lib/sockets";
+export default {
+ async encode({ query: { address } }, res) {
+ let [name, domain] = address.split("@");
+ let url = `https://${domain}/.well-known/lnurlp/${name}`;
+ let r = await got(url).json();
+ if (r.tag !== "payRequest") fail("not an ln address");
+ let enc = bech32.encode("lnurl", bech32.toWords(Buffer.from(url)), 20000);
+ res.send(enc);
+ },
-import axios from "axios";
-import lnurl from "lnurl";
-import jwt from "jsonwebtoken";
-import qs from "query-string";
-import bolt11 from "bolt11";
+ async decode({ query: { text } }, res) {
+ let url = Buffer.from(
+ bech32.fromWords(bech32.decode(text, 20000).words)
+ ).toString();
-import {
- computeConversionFee,
- conversionFeeReceiver
-} from "./lightning/conversionFee.js";
+ res.send(await got(url).json());
+ },
-import send from "$lib/send";
-import sendInternal from "$lib/sendInternal";
-import register from "$lib/register";
+ async lnurlp({ params: { username } }, res) {
+ let uid = await g(`user:${username}`);
+ if (!uid) fail("user not found");
-const logins = persist("data/logins.json");
-const recipients = persist("data/recipients.json");
-const lnurlPayments = persist("data/payments.json");
-const withdrawals = persist("data/withdrawals.json");
+ let id = v4();
+ await s(`lnurl:${id}`, uid);
+ let { URL } = process.env;
+ let host = URL.split("/").at(-1);
-const { HOSTNAME: hostname } = process.env;
-
-export const lnurlServer = lnurl.createServer(config.lnurl);
-
-app.get("/url", async (req, res, next) => {
- try {
- const code = await db.Code.findOne({
- where: {
- code: req.query.code
- }
- });
-
- if (code) {
- res.send(code.text);
- } else {
- throw new Error("code not found");
- }
- } catch (e) {
- err("couldn't find url", e.message);
- }
-});
-
-app.get("/withdraw", auth, async (req, res, next) => {
- const { min, max } = req.query;
- try {
- const result = await lnurlServer.generateNewUrl("withdrawRequest", {
- minWithdrawable: min * 1000,
- maxWithdrawable: max * 1000,
- defaultDescription: "coinos voucher"
- });
-
- withdrawals[result.secret] = req.user;
-
- res.send(result);
- } catch (e) {
- err("problem generating withdrawl url", e.message);
- res.code(500).send(e.message);
- }
-});
-
-app.post("/code", async (req, res, next) => {
- try {
- const { encoded } = req.body.lnurl;
- const code = await db.Code.findOrCreate({
- where: {
- code: `lnurl:${encoded.substr(-32)}`,
- text: encoded
- }
- });
-
- res.send(code);
- } catch (e) {
- err(e.errors);
- res.code(500).send(e.message);
- }
-});
-
-app.post("/withdraw", auth, async (req, res, next) => {
- const { user } = req;
- const {
- amount: value,
- params: { callback, k1 }
- } = req.body;
-
- let invoice;
- let label = new Date();
- let desc = label;
-
- invoice = await ln.invoice(`${value}sats`, label, desc);
-
- const { payment_request: pr } = invoice;
- const url = `${callback}?k1=${k1}&pr=${pr}`;
-
- await db.Invoice.create({
- user_id: user.id,
- text: pr,
- rate: store.rates[user.currency],
- currency: user.currency,
- amount: value,
- tip: 0,
- network: "bitcoin"
- });
-
- try {
- result = (await axios.get(url)).data;
- res.send(result);
- } catch (e) {
- err("failed to withdraw", e.message);
- res.code(500).send(e.message);
- }
-});
-
-app.get("/lnurlp/:username", async (req, res, next) => {
- try {
- const { username } = req.params;
- let user = await db.User.findOne({
- where: {
- username
- }
- });
-
- if (!user) throw new Error("user not found");
-
- let { amount, minSendable, maxSendable } = req.query;
- minSendable = minSendable || 1000;
- maxSendable = maxSendable || 1000000000;
- if (parseInt(amount)) minSendable = maxSendable = amount * 1000;
-
- let result = await lnurlServer.generateNewUrl("payRequest", {
- minSendable,
- maxSendable,
- metadata: JSON.stringify([
- ["text/plain", `paying ${user.username}`],
- ["text/identifier", `${user.username}@coinos.io`]
- ])
- });
-
- recipients[result.secret] = {
- id: user.id,
- currency: user.currency,
- username
- };
-
- l("recipient", user.username, result.secret);
-
- result = await axios.get(result.url);
-
- res.send(result.data);
- } catch (e) {
- err("problem generating payment url", e.message);
- res.code(500).send(e.message);
- }
-});
-
-app.get("/pay/:username", async (req, res, next) => {
- const { username } = req.params;
- let user = await db.User.findOne({
- where: {
- username
- }
- });
-
- let { amount, minSendable, maxSendable } = req.query;
- minSendable = minSendable || 1000;
- maxSendable = maxSendable || 1000000000;
- if (parseInt(amount)) minSendable = maxSendable = amount * 1000;
-
- try {
- const result = await lnurlServer.generateNewUrl("payRequest", {
- minSendable,
- maxSendable,
- metadata: JSON.stringify([
- ["text/plain", `paying ${user.username}`],
- ["text/identifier", `${user.username}@coinos.io`]
- ])
+ res.send({
+ metadata: [
+ ["text/plain", `Paying ${username}@${host}`],
+ ["text/identifier", `${username}@${host}`]
+ ],
+ callback: `${URL}/lnurl/${id}`,
+ tag: "payRequest"
});
+ },
- recipients[result.secret] = {
- id: user.id,
- currency: user.currency,
- username
- };
-
- l("recipient", user.username, result.secret);
- res.send(result);
- } catch (e) {
- err("problem generating payment url", e.message);
- res.code(500).send(e.message);
- }
-});
-
-app.post("/pay", auth, async (req, res, next) => {
- const { user } = req;
-
- const {
- amount,
- comment,
- params: { callback }
- } = req.body;
-
- let url = `${callback}${callback.includes("?") ? "&" : "?"}amount=${amount *
- 1000}${comment ? "&comment=" + comment : ""}`;
+ async lnurl({ params: { id }, query: { amount } }, res) {
+ let pr = await g(`lnurlp:${id}`);
- try {
- const parts = callback.split("/");
- const secret = parts[parts.length - 1];
- lnurlPayments[secret] = user.id;
+ if (!pr) {
+ let uid = await g(`lnurl:${id}`);
+ let user = await g(`user:${uid}`);
- if (recipients[secret]) {
- let data = await sendInternal(
- {
+ ({ text: pr } = await generate({
+ invoice: {
amount,
- memo: comment,
- username: recipients[secret].username
+ type: types.lightning
},
user
- );
+ }));
- res.send(data);
- } else {
- const { data } = await axios.get(url);
- res.send(await send(amount, "", data.pr, user));
+ await s(`lnurlp:${id}`, pr);
}
- } catch (e) {
- console.log(e);
- err("failed to send payment", e.message);
- res.code(500).send(e.message);
- }
-});
-
-app.get("/login", optionalAuth, async (req, res, next) => {
- try {
- const result = await lnurlServer.generateNewUrl("login");
-
- if (req.user) {
- logins[result.secret] = req.user.username;
- }
-
- res.send(result);
- } catch (e) {
- err("problem generating login url", e.message);
- res.code(500).send(e.message);
- }
-});
-app.get("/encode", async (req, res, next) => {
- let { domain, name } = req.query;
- let url = `https://${domain}/.well-known/lnurlp/${name}`;
- res.send(lnurl.encode(url));
-});
-
-app.get("/decode", async (req, res, next) => {
- const { text } = req.query;
-
- try {
- const url = lnurl.decode(text);
- let spl = url.split("?");
- if (spl.length > 1 && qs.parse(spl[1]).tag === "login") {
- return res.send({
- tag: "login",
- k1: qs.parse(spl[1]).k1,
- callback: url,
- domain: url
- .split("://")[1]
- .split("/")[0]
- .split("@")
- .slice(-1)[0]
- .split(":")[0]
- });
- }
-
- const { data: params } = await axios.get(url);
-
- res.send(params);
- } catch (e) {
- err("problem decoding lnurl", e.message);
- res.code(500).send(e.message);
- }
-});
-
-lnurlServer.on("payRequest:action:processed", async function(event) {
- const { secret, params, result } = event;
- const { id, invoice } = result;
- const recipient = recipients[secret];
- let payreq = bolt11.decode(invoice);
-
- try {
- let i = await db.Invoice.create({
- user_id: recipient.id,
- text: invoice,
- rate: store.rates[recipient.currency],
- currency: recipient.currency,
- amount: payreq.satoshis,
- tip: 0,
- network: "lightning"
- });
-
- l("invoice created", i.text, i.amount);
- } catch (e) {
- err("problem finding lnurl invoice", e.message);
- }
-});
-
-lnurlServer.bindToHook(
- "middleware:signedLnurl:afterCheckSignature",
- async (req, res, next) => {
- try {
- let user;
- const { amount: msats, key, tag, pr, k1 } = req.query;
-
- if (msats) {
- let amount = Math.round(msats / 1000);
- const parts = req.originalUrl.split("/");
- const secret = parts[parts.length - 1].split("?")[0];
- let user_id = lnurlPayments[secret];
-
- if (user_id) {
- let account = await db.Account.findOne({
- where: {
- user_id,
- asset: config.liquid.btcasset,
- pubkey: null
- }
- });
-
- if (account.balance < amount) {
- throw new Error("Insufficient funds");
- }
- }
- }
-
- if (tag === "login") {
- let username = logins[k1];
-
- if (!username) {
- const keyObj = await db.Key.findOne({
- where: { hex: key },
- include: [
- {
- model: db.User,
- as: "user"
- }
- ]
- });
-
- if (keyObj) ({ user } = keyObj);
-
- if (!user) {
- let ip =
- req.headers["x-forwarded-for"] || req.connection.remoteAddress;
-
- let username = `satoshi-${key.substr(0, 12)}`;
- let existing = await db.User.findOne({ where: { username } });
- if (existing) user = existing;
- else {
- user = await register(
- {
- username,
- password: key
- },
- ip
- );
- }
- }
-
- ({ username } = user);
- }
-
- logins[key] = { k1, username };
- }
-
- if (pr) {
- user = withdrawals[k1];
- if (!user) {
- if (next) next(new Error("withdrawal not found"));
- return;
- }
-
- try {
- let amount;
-
- let { msatoshi } = await ln.decodepay(pr);
- amount = parseInt(msatoshi / 1000);
- let conversionFee = computeConversionFee(amount);
-
- await db.transaction(async transaction => {
- let account = await db.Account.findOne({
- where: {
- user_id: user.id,
- asset: config.liquid.btcasset
- },
- lock: transaction.LOCK.UPDATE,
- transaction
- });
-
- // account that receives conversion fees
- let receiverAccount = await db.Account.findOne({
- where: {
- "$user.username$": conversionFeeReceiver
- },
- include: [
- {
- model: db.User,
- as: "user"
- }
- ],
- lock: transaction.LOCK.UPDATE,
- transaction
- });
-
- let conversionFeeDeduction = Math.min(
- account.lightning_credits,
- conversionFee
- );
- if (conversionFeeDeduction) {
- await account.decrement(
- { lightning_credits: conversionFeeDeduction },
- { transaction }
- );
- await account.reload({ transaction });
- conversionFee -= conversionFeeDeduction;
- }
-
- if (account.balance < amount) {
- throw new Error("Insufficient funds");
- } else if (account.balance < amount + conversionFee) {
- throw new Error("Insufficient funds for conversion fee");
- }
-
- let fee_payment_id = null;
- if (conversionFee) {
- await receiverAccount.increment(
- { balance: conversionFee },
- { transaction }
- );
- await receiverAccount.reload({ transaction });
- let 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;
- }
-
- let payment = await db.Payment.create(
- {
- amount: -amount,
- account_id: account.id,
- user_id: user.id,
- hash: pr,
- rate: store.rates[user.currency],
- currency: user.currency,
- confirmed: true,
- network: "lightning",
- fee_payment_id
- },
- { transaction }
- );
-
- setTimeout(async () => {
- try {
- let { payments } = await ln.getPayments({
- limit: 5
- });
-
- let p = payments.find(p => p.request === pr);
- if (p) {
- l("found payment", pr);
- payment.fee = p.fee;
- await account.decrement({ balance: p.fee }, { transaction });
- await account.reload({ transaction });
- await payment.save();
- payment = payment.get({ plain: true });
- payment.account = account.get({ plain: true });
- emit(user.username, "account", account);
- emit(user.username, "payment", payment);
- } else {
- warn("payment not found, fee not set", pr);
- }
- } catch (e) {
- err("problem trying to get ln withdrawal payment", e);
- }
- }, 1000);
-
- await account.decrement(
- { balance: amount + conversionFee },
- { transaction }
- );
- await account.reload({ transaction });
-
- let p = payment.get({ plain: true });
- p.account = account.get({ plain: true });
-
- emit(user.username, "account", p.account);
- emit(user.username, "payment", p);
- });
- } catch (e) {
- err("failed to process withdrawal", e.message);
- return next(e);
- }
- }
-
- if (next) next();
- } catch (e) {
- err("unhandled lnurerr", e.message);
- }
- }
-);
-
-lnurlServer.bindToHook("login", async key => {
- try {
- if (!key) throw new Error("login key not defined");
-
- const exists = await db.Key.findOne({
- where: { hex: key },
- include: [{ model: db.User, as: "user" }]
- });
-
- let user;
- if (logins[key] && logins[key] !== "undefined") {
- const { username } = logins[key];
- user = await db.User.findOne({
- where: { username }
- });
-
- if (user) {
- const [k, created] = await db.Key.findOrCreate({
- where: {
- user_id: user.id,
- hex: key
- }
- });
-
- if (created) {
- l("added key", username, k);
- emit(username, "key", k);
- }
- } else {
- l("user not found");
- user = await register({
- username: key.substr(0, 8),
- password: key
- });
- }
- } else if (exists) ({ user } = exists);
-
- if (user && user.username) {
- const payload = { username: user.username };
- const token = jwt.sign(payload, config.jwt);
- const ws = store.sessions[logins[key].k1];
- if (ws && ws.send)
- ws.send(JSON.stringify({ type: "token", data: { token, key } }));
- }
- } catch (e) {
- err("problem with login hook", e.message);
+ res.send({ pr });
}
-});
+};
diff --git a/routes/locations.js b/routes/locations.js
index 3e0fee4c..fb4487f7 100644
--- a/routes/locations.js
+++ b/routes/locations.js
@@ -1,19 +1,7 @@
-import app from "$app";
-import redis from "$lib/redis";
-import axios from "axios";
+import { g } from "$lib/db";
-let locations;
-app.get("/locations", async (req, res) => {
- res.send({ locations });
-});
-
-let getLocations = async () => {
- let { data } = await axios.get('https://api.btcmap.org/v2/elements?updated_since=2022-09-19');
- locations = data.filter(
- (l) => l['osm_json'].tags && l['osm_json'].tags['payment:coinos'] === 'yes'
- );
-
- setTimeout(getLocations, 60000);
-}
-
-getLocations();
+export default {
+ async list(req, res) {
+ res.send({ locations: await g("locations") });
+ }
+};
diff --git a/routes/nostr.js b/routes/nostr.js
new file mode 100644
index 00000000..005a4be9
--- /dev/null
+++ b/routes/nostr.js
@@ -0,0 +1,157 @@
+import { nada, uniq } from "$lib/utils";
+import config from "$config";
+import { g, s, db } from "$lib/db";
+import { pool, q } from "$lib/nostr";
+
+export default {
+ async event({ params: { id } }, res) {
+ let event = await g(`ev:${id}`);
+ let { pubkey } = event;
+
+ event.user = (await g(`user:${pubkey}`)) || {
+ username: pubkey.substr(0, 6),
+ pubkey,
+ anon: true,
+ follows: [],
+ followers: []
+ };
+
+ res.send(event);
+ },
+
+ async notes({ params: { pubkey } }, res) {
+ let user = await g(`user:${pubkey}`);
+
+ if (!user)
+ user = {
+ username: pubkey.substr(0, 6),
+ pubkey,
+ anon: true,
+ follows: [],
+ followers: []
+ };
+
+ let params = {
+ kinds: [1],
+ authors: [pubkey]
+ },
+ opts = { since: 0 };
+
+ q(`${pubkey}:notes`, params, opts).catch(nada);
+
+ await s(`user:${pubkey}`, user);
+
+ let ids = await db.sMembers(pubkey);
+
+ let events = ids.length
+ ? (await db.mGet(ids.map(k => "ev:" + k))).map(JSON.parse)
+ : [];
+
+ res.send(events.map(e => ({ ...e, user })));
+ },
+
+ async broadcast(req, res) {
+ let { event } = req.body;
+ pool.send(["EVENT", event]);
+ res.send(event);
+ },
+
+ async follows({ params: { pubkey }, query: { tagsonly } }, res) {
+ let sub = `${pubkey}:follows`,
+ params = {
+ limit: 1,
+ kinds: [3],
+ authors: [pubkey]
+ },
+ opts = { timeout: 60000, eager: 60000 };
+
+ q(sub, params, opts).catch(nada);
+
+ let tags = (await g(`${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 = await g(`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);
+ },
+
+ async followers({ params: { pubkey } }, res) {
+ try {
+ let pubkeys = [
+ ...new Set([...(await db.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 = await g(`user:${pubkey}`);
+ if (!user || !user.updated) {
+ q(`${pubkey}:profile:f2`, {
+ limit: 1,
+ kinds: [0],
+ authors: [pubkey]
+ }).catch(nada);
+
+ user = await g(`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);
+ }
+ },
+
+ async identities(req, res) {
+ res.send({
+ names: {
+ adam:
+ "566c166f3adab0c8fba5da015b0b3bcc8eb3696b455f2a1d43bfbd97059646a8",
+ asoltys:
+ "566c166f3adab0c8fba5da015b0b3bcc8eb3696b455f2a1d43bfbd97059646a8"
+ }
+ });
+ }
+};
diff --git a/routes/payments.js b/routes/payments.js
index 416bc20a..1043959c 100644
--- a/routes/payments.js
+++ b/routes/payments.js
@@ -1,163 +1,258 @@
-import app from "$app";
-import db from "$db";
import config from "$config";
import store from "$lib/store";
-
-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 { l, warn } from "$lib/logging";
-import { fail } from "$lib/utils";
+import { emit } from "$lib/sockets";
+import { v4 } from "uuid";
+import { db, g, s, t } from "$lib/db";
+import { l, err } from "$lib/logging";
+import { fail, btc, sats } from "$lib/utils";
+import { requirePin } from "$lib/auth";
+import { debit, credit, confirm, types } from "$lib/payments";
+import got from "got";
import bc from "$lib/bitcoin";
-import lq from "$lib/liquid";
+import ln from "$lib/ln";
-import btcRoutes from "./bitcoin/index";
-import lnRoutes from "./lightning/index";
-import lqRoutes from "./liquid/index";
+export default {
+ async create({ body, user }, res) {
+ let { amount, hash, maxfee, name, memo, payreq, tip, username } = body;
-import "./bitcoin/receive";
-import "./lightning/receive";
-import "./liquid/receive";
+ amount = parseInt(amount);
+ maxfee = parseInt(maxfee);
+ tip = parseInt(tip);
-app.post("/send", auth, send);
-app.post("/sendToTokenHolders", auth, async (req, res, next) => {
- let { asset, amount } = req.body;
+ await requirePin({ body, user });
- let accounts = await db.Account.findAll({
- where: {
- asset,
- "$user.username$": { [Op.ne]: "gh" }
- },
+ let p;
- include: [{ model: db.User, as: "user" }]
- });
+ if (username && username.endsWith("@classic")) {
+ let { username: source } = user;
+ username = username.replace("@classic", "");
+ p = await debit(hash, amount, 0, memo, user, types.classic);
- let totalTokens = accounts.reduce((a, b) => a + b.balance, 0);
+ await got
+ .post(`${config.classic}/admin/credit`, {
+ json: { username, amount, source },
+ headers: { authorization: `Bearer ${config.admin}` }
+ })
+ .json();
+ } else if (payreq) {
+ p = await debit(
+ hash,
+ amount + maxfee,
+ maxfee,
+ memo,
+ user,
+ types.lightning
+ );
- let totalSats = Math.floor(amount / totalTokens);
+ let r = await ln.pay(payreq);
- if (totalSats < 1) throw new Error("amount is too low to distribute");
+ p.amount = -amount;
+ p.hash = r.payment_hash;
+ p.fee = r.msatoshi_sent - r.msatoshi;
+ p.ref = r.payment_preimage;
- 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 }));
+ await s(`payment:${p.id}`, p);
+ await db.incrBy(`balance:${p.uid}`, maxfee - p.fee);
+ } else if (hash) {
+ p = await debit(hash, amount, 0, memo, user);
+ await credit(hash, amount, memo, user.id);
+ } else {
+ let pot = name || v4();
+ p = await debit(hash, amount, 0, memo, user, types.pot);
+ await db.incrBy(`pot:${pot}`, amount);
+ await db.lPush(`pot:${pot}:payments`, p.id);
+ l("funded pot", pot);
+ }
- res.send({ success: "it worked" });
-});
+ res.send(p);
+ },
-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");
-});
+ async list({ user: { id }, query: { start, end, limit, offset } }, res) {
+ if (limit) limit = parseInt(limit);
+ offset = parseInt(offset) || 0;
-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);
+ let payments = (await db.lRange(`${id}:payments`, 0, -1)) || [];
+ payments = (
+ await Promise.all(
+ payments.map(async id => {
+ let p = await g(`payment:${id}`);
+ if (p.created < start || p.created > end) return;
+ if (p.type === types.internal) p.with = await g(`user:${p.ref}`);
+ return p;
+ })
+ )
+ )
+ .filter(p => p)
+ .sort((a, b) => b.created - a.created);
-if (config.lnurl) {
- let { channelRequest } = await import("$routes/lightning/channelRequest");
- app.post("/lightning/channelRequest", channelRequest);
-}
+ let total = payments.length;
-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);
+ if (limit) payments = payments.slice(offset, offset + limit);
-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);
+ res.send({ payments, total });
+ },
-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);
+ async get({ params: { hash } }, res) {
+ res.send(await g(`payment:${hash}`));
+ },
-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);
+ async parse({ body: { payreq } }, res) {
+ let hour = 1000 * 60 * 60;
+ let { last } = store.nodes;
+ let { nodes } = store;
-app.get("/payments", auth, async (req, res) => {
- let { start, end, limit, offset, v2 } = req.query;
+ if (!last || last > Date.now() - hour) ({ nodes } = await ln.listnodes());
+ store.nodes = nodes;
- if (limit) limit = parseInt(limit);
- if (offset) offset = parseInt(offset);
+ let twoWeeksAgo = new Date(new Date().setDate(new Date().getDate() - 14));
+ let decoded = await ln.decodepay(payreq);
+ let { msatoshi, payee } = decoded;
+ let node = nodes.find(n => n.nodeid === payee);
+ let alias = node ? node.alias : payee.substr(0, 12);
- let where = {
- account_id: req.user.account_id,
- };
+ res.send({ alias, amount: Math.round(msatoshi / 1000) });
+ },
- 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));
+ async pot({ params: { name } }, res) {
+ let amount = await g(`pot:${name}`);
+ let payments = (await db.lRange(`pot:${name}:payments`, 0, -1)) || [];
+ payments = await Promise.all(payments.map(hash => g(`payment:${hash}`)));
- if (!req.user.account_id) return res.send([]);
+ await Promise.all(
+ payments.map(async p => (p.user = await g(`user:${p.uid}`)))
+ );
- let total = await db.Payment.count({ where });
+ payments = payments.filter(p => p);
+ res.send({ amount, payments });
+ },
- let payments = await db.Payment.findAll({
- where,
- order: [["id", "DESC"]],
- include: {
- model: db.Account,
- as: "account"
- },
- include: {
- attributes: ["username", "profile"],
- model: db.User,
- as: "with"
- },
- limit,
- offset
- });
+ async take({ body: { name, amount }, user }, res) {
+ amount = parseInt(amount);
+ await t(`pot:${name}`, async (balance, db) => {
+ if (balance < amount) fail("Insufficient funds");
+ await db
+ .multi()
+ .decrBy(`pot:${name}`, amount)
+ .exec();
+ });
- if (v2) {
- res.send({ transactions: payments, total });
- } else {
- res.send(payments);
- }
-});
+ let hash = v4();
+ await s(`invoice:${hash}`, {
+ uid: user.id,
+ received: 0
+ });
-app.get("/payment/:redeemcode", async (req, res) => {
- try {
- const { redeemcode } = req.params;
- let payment = await db.Payment.findOne({
- where: {
- redeemcode
- },
- include: {
- model: db.Account,
- as: "account"
+ let payment = await credit(hash, amount, "", name, types.pot);
+ await db.lPush(`pot:${name}:payments`, hash);
+
+ res.send({ payment });
+ },
+
+ async bitcoin({ body: { txid, wallet } }, res) {
+ if (wallet === config.bitcoin.wallet) {
+ let { confirmations, details } = await bc.getTransaction(txid);
+ for (let { address, amount, vout } of details) {
+ if (confirmations > 0) {
+ await confirm(address, txid, vout);
+ } else {
+ await credit(
+ address,
+ sats(amount),
+ "",
+ `${txid}:${vout}`,
+ types.bitcoin
+ );
+ }
}
+ }
+ res.send({});
+ },
+
+ async fee(req, res) {
+ let { amount, address, feeRate, subtract } = req.body;
+ let subtractFeeFromOutputs = subtract ? [0] : [];
+ let replaceable = true;
+ let outs = [{ [address]: btc(amount) }];
+ let count = await bc.getBlockCount();
+
+ let raw = await bc.createRawTransaction([], outs, 0, replaceable);
+ let tx = await bc.fundRawTransaction(raw, {
+ feeRate,
+ subtractFeeFromOutputs,
+ replaceable
});
- if (!payment) fail("invalid code");
+ if (config.bitcoin.walletpass)
+ await bc.walletPassphrase(config.bitcoin.walletpass, 300);
+
+ let { hex } = await bc.signRawTransactionWithWallet(tx.hex);
+ let { vsize } = await bc.decodeRawTransaction(hex);
+ feeRate = Math.round((sats(tx.fee) * 1000) / vsize);
+
+ res.send({ feeRate, tx });
+ },
+
+ async send(req, res) {
+ await requirePin(req);
+
+ let { user } = req;
+ let { address, memo, tx } = req.body;
+ let { hex, fee } = tx;
+
+ if (config.bitcoin.walletpass)
+ await bc.walletPassphrase(config.bitcoin.walletpass, 300);
+
+ ({ hex } = await bc.signRawTransactionWithWallet(hex));
+
+ let r = await bc.testMempoolAccept([hex]);
+ if (!r[0].allowed) fail("transaction rejected");
+
+ fee = sats(fee);
+ if (fee < 0) fail("fee cannot be negative");
+
+ tx = await bc.decodeRawTransaction(hex);
+ let { txid } = tx;
+
+ let total = 0;
+ let change = 0;
+
+ for (let {
+ scriptPubKey: { address },
+ value
+ } of tx.vout) {
+ total += sats(value);
+ if (
+ (await bc.getAddressInfo(address)).ismine &&
+ !(await g(`invoice:${address}`))
+ )
+ change += sats(value);
+ }
+
+ total = total - change + fee;
+ let amount = total - fee;
+
+ if (change && !amount) fail("Cannot send to unregistered coinos address");
+
+ await debit(txid, amount, fee, null, user, types.bitcoin, txid);
+ await bc.sendRawTransaction(hex);
+
+ res.send({ txid });
+ },
+
+ async del({ params: { username }, headers: { authorization } }, res) {
+ if (!authorization.includes(config.admin))
+ return res.code(401).send("unauthorized");
+ let { id, pubkey } = await g(`user:${await g(`user:${username}`)}`);
+ let invoices = await db.lRange(`${id}:invoices`, 0, -1);
+ let payments = await db.lRange(`${id}:payments`, 0, -1);
+
+ for (let { id } of invoices) db.del(`invoice:${id}`);
+ for (let { id } of payments) db.del(`payment:${id}`);
+ db.del(`user:${username}`);
+ db.del(`user:${id}`);
+ db.del(`user:${pubkey}`);
- res.send(payment);
- } catch (e) {
- res.code(500).send(e.message);
+ res.send({});
}
-});
+};
diff --git a/routes/rates.js b/routes/rates.js
new file mode 100644
index 00000000..c36c7ae8
--- /dev/null
+++ b/routes/rates.js
@@ -0,0 +1,11 @@
+import store from "$lib/store";
+
+export default {
+ async last(req, res) {
+ res.send(store.last);
+ },
+
+ async index(req, res) {
+ res.send(store.rates);
+ }
+};
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..79af1fb6 100644
--- a/routes/requests.js
+++ b/routes/requests.js
@@ -1,137 +1,49 @@
-import app from "$app";
-import db from "$db";
-import { auth } from "$lib/passport";
import { emit } from "$lib/sockets";
-import { Op, col } from "@sequelize/core";
+import { g, db } from "$lib/db";
-app.get("/request/:id", auth, async (req, res) => {
- try {
- let request = await db.Request.findOne({
- where: { id: req.params.id },
- include: [
- {
- attributes: ["username", "profile"],
- model: db.User,
- as: "recipient"
- },
- {
- attributes: ["username", "profile"],
- model: db.User,
- as: "requester"
- },
- {
- model: db.Invoice,
- as: "invoice"
- }
- ]
- });
+export default {
+ async get({ params: { id } }, res) {
+ try {
+ res.send({ request: await g(id) });
+ } catch (e) {
+ console.log(e);
+ res.code(500).send(e.message);
+ }
+ },
- res.send({ request });
- } catch (e) {
- console.log(e);
- res.code(500).send(e.message);
- }
-});
-
-app.get("/requests", auth, async (req, res) => {
- try {
- const day = new Date(new Date().setDate(new Date().getDate() - 1));
+ async list({ user: { id } }, res) {
+ try {
+ const day = new Date(new Date().setDate(new Date().getDate() - 1));
- let invoices = (
- await db.Request.findAll({
- where: {
- [Op.or]: {
- recipient_id: req.user.id,
- requester_id: req.user.id
- },
- createdAt: { [Op.gt]: day }
- },
- order: [["createdAt", "DESC"]],
- include: [
- {
- model: db.Invoice,
- as: "invoice",
- where: { received: { [Op.lt]: col("invoice.amount") } },
- include: {
- model: db.User,
- as: "user",
- attributes: ["username", "profile"]
- }
- },
- {
- model: db.User,
- as: "requester",
- attributes: ["username", "profile"]
- }
- ]
- })
- )
- .map(
- r =>
- r.invoice && {
- request_id: r.id,
- requester: r.requester.get({ plain: true }),
- ...r.invoice.get({ plain: true })
- }
- )
- .filter(r => r);
+ let invoices = await db.lRange(`${id}:invoices`, 0, -1);
+ let requests = await db.lRange(`${id}:requests`, 0, -1);
- let requests = await db.Request.findAll({
- where: {
- recipient_id: req.user.id,
- createdAt: { [Op.gt]: day },
- invoice_id: { [Op.eq]: null }
- },
- order: [["createdAt", "DESC"]],
- include: {
- attributes: ["username", "profile"],
- model: db.User,
- as: "requester"
- }
- });
-
- res.send({ invoices, requests });
- } catch (e) {
- console.log(e);
- res.code(500).send(e.message);
- }
-});
+ res.send({ invoices, requests });
+ } catch (e) {
+ console.log(e);
+ res.code(500).send(e.message);
+ }
+ },
-app.post("/requests", auth, async (req, res) => {
- try {
- let { recipient: username, ...params } = req.body;
- let { id: recipient_id } = await db.User.findOne({ where: { username } });
+ async create(
+ { body: { recipient, ...params }, user: { username, profile } },
+ res
+ ) {
+ let { id: recipient_id } = await g(`user:${recipient}`);
- let request = await db.Request.create({ recipient_id, ...params });
- request = request.get({ plain: true });
+ let request = { recipient_id, ...params };
request.requester = {
- username: req.user.username,
- profile: req.user.profile
+ username,
+ profile
};
- emit(username, "request", request);
- res.send(request);
- } catch (e) {
- console.log(e);
- res.code(500).send(e.message);
- }
-});
+ emit(recipient, "request", request);
-app.post("/requests/delete", auth, async (req, res) => {
- try {
- let r = await db.Request.findOne({
- where: {
- id: req.body.request_id,
- [Op.or]: { requester_id: req.user.id, recipient_id: req.user.id }
- }
- });
-
- if (!r) throw new Error("request not found");
- await r.destroy();
+ res.send(request);
+ },
+ async destroy({ body: { request_id }, user: { id } }, res) {
+ await db.lrem(`user:${id}:requests`, request_id);
res.send();
- } catch (e) {
- console.log(e);
- res.code(500).send(e.message);
}
-});
+};
diff --git a/routes/send.js b/routes/send.js
deleted file mode 100644
index 591b5ab7..00000000
--- a/routes/send.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import { err } from "$lib/logging";
-import sendInternal from "$lib/sendInternal";
-import { requirePin } from "$lib/utils";
-
-export default async (req, res, next) => {
- try {
- await requirePin(req);
- let payment = await sendInternal(req.body, req.user);
- res.send(payment);
- } catch (e) {
- err("problem sending internal payment", e.message);
- return res.code(500).send(e.message);
- }
-};
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 02e7e3ed..17d4e1da 100644
--- a/routes/users.js
+++ b/routes/users.js
@@ -1,693 +1,394 @@
-import app from "$app";
+import { g, s, db } from "$lib/db";
import config from "$config";
import store from "$lib/store";
-import { auth, optionalAuth } from "$lib/passport";
-import { getUser } from "$lib/utils";
-import axios from "axios";
+import { fields, nada, pick, uniq, wait } from "$lib/utils";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import { authenticator } from "otplib";
-import getAccount from "$lib/account";
-import Sequelize from "@sequelize/core";
-import bitcoin from "bitcoinjs-lib";
-import liquid from "liquidjs-lib";
-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 { v4 } from "uuid";
+import { parseISO } from "date-fns";
+import { types } from "$lib/payments";
-const pick = (O, ...K) => K.reduce((o, k) => ((o[k] = O[k]), o), {});
+import got from "got";
+import upload from "$lib/upload";
-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) {
- res.code(500).send(e.message);
- }
-});
+export default {
+ upload,
-app.get("/users/:username", async (req, res) => {
- const { username } = req.params;
+ async me({ user }, res) {
+ try {
+ user.balance = await g(`balance:${user.id}`);
+ user.prompt = !!user.prompt;
+ res.send(pick(user, whitelist));
+ } catch (e) {
+ console.log("problem fetching user", e);
+ res.code(500).send(e.message);
+ }
+ },
- const user = await db.User.findOne({
- attributes: ["username", "banner", "profile", "address", "currency"],
- where: Sequelize.where(
- Sequelize.fn("lower", Sequelize.col("username")),
- username.toLowerCase()
- )
- });
+ async get({ params: { key } }, res) {
+ try {
+ if (key.startsWith("npub")) {
+ try {
+ key = Buffer.from(fromWords(decode(key).words)).toString("hex");
+ } catch (e) {}
+ }
- if (user) res.send(user);
- else res.code(500).send("User not found");
-});
+ let user = await g(`user:${key}`);
+ if (typeof user === "string") {
+ user = await g(`user:${user}`);
+ }
-app.post("/register", async (req, res) => {
- try {
- const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress;
- const user = await register(req.body.user, ip, false);
- res.send(pick(user, ...whitelist));
- } catch (e) {
- res.code(500).send(e.message);
- }
-});
+ if (!user && key.length === 64) {
+ user = {
+ currency: "USD",
+ username: key,
+ display: key.substr(0, 6),
+ pubkey: key,
+ anon: true
+ };
+ }
-app.post("/disable2fa", auth, async (req, res) => {
- try {
- let {
- user,
- body: { token }
- } = req;
+ if (!user) return res.code(500).send("User not found");
- if (user.twofa && !authenticator.check(token, user.otpsecret)) {
+ let whitelist = [
+ "username",
+ "banner",
+ "profile",
+ "address",
+ "currency",
+ "pubkey",
+ "display",
+ "prompt",
+ "id"
+ ];
+
+ user.prompt = !!user.prompt;
+
+ res.send(pick(user, whitelist));
+ } catch (e) {
+ console.log(e);
+ res.code(500).send(e.message);
+ }
+ },
+
+ async create(req, res) {
+ try {
+ const ip = req.headers["x-forwarded-for"] || req.socket.remoteAddress;
+ let { cipher, pubkey, password, username, salt } = req.body.user;
+
+ let user = {
+ cipher,
+ pubkey,
+ password,
+ username,
+ salt
+ };
+
+ user = await register(user, ip, false);
+ l("registered new user", username);
+ res.send(pick(user, whitelist));
+ } catch (e) {
+ res.code(500).send(e.message);
+ }
+ },
+
+ async disable2fa({ user, body: { token } }, res) {
+ let { id, twofa, username, otpsecret } = user;
+ if (twofa && !authenticator.check(token, otpsecret)) {
return res.code(401).send("2fa required");
}
user.twofa = false;
- await user.save();
- emit(user.username, "user", user);
- emit(user.username, "otpsecret", user.otpsecret);
- l("disabled 2fa", user.username);
+ await s(`user:${id}`, user);
+ emit(username, "user", user);
+ emit(username, "otpsecret", user.otpsecret);
+ l("disabled 2fa", username);
res.send({});
- } catch (e) {
- res.code(500).send("Problem disabling 2fa");
- }
-});
+ },
-app.post("/2fa", auth, async (req, res) => {
- let { user } = req;
- try {
- const isValid = authenticator.check(req.body.token, req.user.otpsecret);
+ async enable2fa({ user, body: { token } }, res) {
+ let { id, otpsecret, username } = user;
+ const isValid = authenticator.check(token, otpsecret);
if (isValid) {
user.twofa = true;
- user.save();
- emit(user.username, "user", req.user);
+ await s(`user:${id}`, user);
+ emit(username, "user", user);
} else {
return res.code(500).send("Invalid token");
}
- } catch (e) {
- err("error setting up 2fa", e);
- }
- l("enabled 2fa", user.username);
- res.send({});
-});
-
-app.get("/exists", async (req, res) => {
- let exists = await db.User.findOne({
- where: { username: req.query.username }
- });
+ l("enabled 2fa", username);
+ res.send({});
+ },
- res.send(!!exists);
-});
+ async update({ user, body }, res) {
+ l("updating user", user.username);
-app.post("/user", auth, async (req, res) => {
- try {
- let { user } = req;
- let {
- address,
- confirm,
- currencies,
- currency,
- email,
- fiat,
- password,
- newpin,
- pin,
- seed,
- tokens,
- twofa,
- unit,
- username
- } = req.body;
+ let { confirm, password, pin, newpin, username } = body;
if (user.pin && !(pin === user.pin)) throw new Error("Pin required");
+ if (typeof newpin !== "undefined") user.pin = newpin;
+ if (!user.pin || user.pin === "undefined") delete user.pin;
- let exists = await db.User.findOne({
- where: { username }
- });
+ let exists;
+ if (username) exists = await g(`user:${username}`);
let token;
if (user.username !== username && exists) {
err("username taken", username, user.username, exists.username);
throw new Error("Username taken");
- } else {
- store.sockets[username] = store.sockets[user.username];
+ } else if (username) {
if (user.username !== username)
l("changing username", user.username, username);
user.username = username;
-
- token = jwt.sign({ username }, config.jwt);
- res.cookie("token", token, {
- expires: new Date(Date.now() + 432000000)
- });
}
- if (unit) user.unit = unit;
- user.currency = currency;
- user.currencies = currencies;
- user.pin = newpin;
- user.tokens = tokens;
- user.twofa = twofa;
- user.seed = seed;
- user.fiat = fiat;
- user.email = email;
- user.address = address;
+ let attributes = [
+ "address",
+ "cipher",
+ "currencies",
+ "currency",
+ "display",
+ "email",
+ "fiat",
+ "locktime",
+ "prompt",
+ "pubkey",
+ "salt",
+ "seed",
+ "tokens",
+ "twofa"
+ ];
+
+ for (let a of attributes) {
+ if (body[a]) user[a] = body[a];
+ }
if (password && password === confirm) {
user.password = await bcrypt.hash(password, 1);
}
- await user.save();
-
- user = user.get({ plain: true });
user.haspin = !!user.pin;
+ await s(`user:${user.id}`, user);
emit(user.username, "user", user);
res.send({ user, token });
- } catch (e) {
- err("error updating user", e.message);
- res.code(500).send(e.message);
- }
-});
-
-app.post("/keys", auth, async (req, res) => {
- const { key: hex } = req.body;
- const key = await db.Key.create({
- user_id: req.user.id,
- hex
- });
- emit(req.user.username, "key", key);
- res.send(key);
-});
-
-app.post("/keys/delete", auth, async (req, res) => {
- const { hex } = req.body;
- (
- await db.Key.findOne({
- where: {
- user_id: req.user.id,
- hex
- }
- })
- ).destroy();
-});
-
-app.post("/updateSeeds", auth, async (req, res) => {
- let { user } = req;
- let { seeds } = req.body;
- let keys = Object.keys(seeds);
-
- for (let i = 0; i < keys.length; i++) {
- let id = keys[i];
- let seed = seeds[id];
- await db.Account.update(
- { seed },
- {
- where: { id, user_id: user.id }
- }
- );
-
- const account = await db.Account.findOne({
- where: { id }
- });
-
- emit(user.username, "account", account);
- }
-
- res.send({});
-});
-
-app.post("/accounts/delete", auth, async (req, res) => {
- const { id } = req.body;
- const account = await db.Account.findOne({ where: { id } });
- if (account) await account.destroy();
- res.send({});
-});
-
-app.post("/accounts", auth, async (req, res) => {
- const {
- name,
- seed,
- pubkey,
- ticker,
- precision,
- path,
- privkey,
- network
- } = req.body;
- const { user } = req;
-
- let account = await db.Account.create({
- user_id: user.id,
- asset: config.liquid.btcasset,
- balance: 0,
- pending: 0,
- name,
- ticker,
- precision,
- pubkey,
- privkey,
- seed,
- path,
- network
- });
-
- emit(user.username, "account", account);
-
- if (pubkey) user.index++;
- user.account_id = account.id;
- await user.save();
- user.account = account;
- emit(user.username, "user", user);
-
- res.send(account);
-});
-
-let login = async (req, res) => {
- try {
- const { params, sig, key } = req.body;
-
- if (sig) {
- const { callback } = params;
-
- try {
- const url = `${callback}&sig=${sig}&key=${key}`;
- const response = await axios.get(url);
- res.send(response.data);
- } catch (e) {
- err("problem calling lnurl login", e.message);
- res.code(500).send(e.message);
- }
+ },
- return;
- }
+ async login(req, res) {
+ try {
+ const { username, password, token: twofa } = req.body;
+ l("logging in", username);
- let twofa = req.body.token;
+ let uid = await g(`user:${username}`);
+ let user = await g(`user:${uid}`);
- let user = await getUser(req.body.username);
+ if (!(user && user.migrated)) {
+ uid = user ? user.id : v4();
- if (
- !user ||
- (user.password &&
- !(await bcrypt.compare(req.body.password, user.password)))
- ) {
- warn("invalid username or password attempt", req.body.username);
- return res.code(401).send({});
- }
+ let { classic } = config;
+ try {
+ let { token } = await got
+ .post(`${classic}/login`, { json: { username, password } })
+ .json();
- if (
- user.twofa &&
- (typeof twofa === "undefined" ||
- !authenticator.check(twofa, user.otpsecret))
- ) {
- return res.code(401).send("2fa required");
- }
+ if (!token) fail();
- l(
- "login",
- req.body.username,
- req.headers["x-forwarded-for"] || req.connection.remoteAddress
- );
+ user = await got(`${classic}/admin/migrate/${username}?zero=true`, {
+ headers: { authorization: `Bearer ${config.admin}` }
+ }).json();
- let payload = { username: user.username };
- let token = jwt.sign(payload, config.jwt);
- res.cookie("token", token, { expires: new Date(Date.now() + 432000000) });
- user.accounts = await user.getAccounts();
- user.keys = await user.getKeys();
- user = pick(user, ...whitelist);
- res.send({ user, token });
- } catch (e) {
- err("login error", e.message, req.connection.remoteAddress);
- res.code(401).send({});
- }
-};
+ let { balance, pubkey } = user;
+ if (!user) fail();
-app.post("/login", login);
-app.post("/taboggan", login);
-app.post("/doggin", login);
+ uid = user.uuid;
-app.post("/logout", optionalAuth, async (req, res) => {
- let { subscription } = req.body;
- if (!subscription) return res.send({});
+ user = {
+ ...pick(user, fields),
+ id: uid,
+ about: user.address,
+ migrated: true
+ };
- const { username } = req.user;
+ await s(`user:${pubkey}`, uid);
+ await s(`user:${username}`, uid);
+ await s(`user:${uid}`, user);
+ await s(`balance:${uid}`, balance);
- if (username) {
- l("logging out", username);
- let i = req.user.subscriptions.findIndex(
- s => JSON.stringify(s) === subscription
- );
- if (i > -1) {
- req.user.subscriptions.splice(i, 1);
- }
- await req.user.save();
- Object.keys(logins).map(
- k => logins[k]["username"] === username && delete logins[k]
- );
- }
+ let payments = await got(`${classic}/payments`, {
+ headers: { authorization: `Bearer ${token}` }
+ }).json();
- res.send({});
-});
+ for (let p of payments) {
+ let n = pick(p, ["amount", "fee", "confirmed", "rate", "currency"]);
+ n.id = v4();
+ n.created = parseISO(p.createdAt).getTime();
+ n.type = p.network;
+ if (!["bitcoin", "lightning"].includes(n.type))
+ n.type = types.internal;
-app.post("/account", auth, async (req, res) => {
- const { user } = req;
- const {
- id,
- name,
- ticker,
- precision,
- domain,
- seed,
- pubkey,
- privkey,
- path,
- hide,
- index
- } = req.body;
+ if (n.type === types.internal) {
+ if (!p.with) continue;
- try {
- await db.transaction(async transaction => {
- let account = await db.Account.findOne({
- where: { id, user_id: user.id },
- lock: transaction.LOCK.UPDATE,
- transaction
- });
+ let id = await g(`user:${p.with.username}`);
+ let u = id && (await g(`user:${id}`));
- let params = {
- name,
- ticker,
- precision,
- domain,
- seed,
- pubkey,
- path,
- hide,
- index,
- privkey
- };
+ if (!u) {
+ u = await got(`${classic}/admin/migrate/${p.with.username}`, {
+ headers: { authorization: `Bearer ${config.admin}` }
+ }).json();
- if (!account || (account.pubkey && !pubkey)) delete params.pubkey;
+ u = { id: u.uuid, about: u.address, ...pick(u, fields) };
+ delete u.address;
- await db.Account.update(params, {
- where: { id, user_id: user.id },
- transaction
- });
+ await s(`user:${u.pubkey}`, u.id);
+ await s(`user:${p.with.username}`, u.id);
+ await s(`user:${u.id}`, u);
- 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;
+ l("added missing user", u.username);
+ }
- try {
- const account = await db.Account.findOne({
- where: { id }
- });
+ if (!(u && u.id)) continue;
+ n.ref = u.id;
+ }
- if (account.user_id !== user.id)
- return res.code(500).send("Failed to open wallet");
+ await s(`payment:${n.id}`, n);
+ await db.lPush(`${uid}:payments`, n.id);
+ }
- 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"
+ l("migrated user", user.username);
+ } catch (e) {
+ console.log(e);
+ }
}
- });
-
- 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);
-});
-
-app.post("/subscribe", auth, async function(req, res) {
- let { subscriptions } = req.user;
- let { subscription } = req.body;
- if (!subscriptions) subscriptions = [];
- if (
- !subscriptions.find(s => JSON.stringify(s) === JSON.stringify(subscription))
- )
- subscriptions.push(subscription);
- req.user.subscriptions = subscriptions;
- l("subscribing", req.user.username);
- await req.user.save();
- res.sendStatus(201);
-});
-
-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 });
+ if (
+ !user ||
+ (user.password && !(await bcrypt.compare(password, user.password)))
+ ) {
+ warn("invalid username or password attempt", username);
+ return res.code(401).send({});
}
- let account = await getAccount(source.account.asset, user, transaction);
- let { hash, memo, confirmed, fee, network } = source;
-
- source.redeemed = true;
- await source.save({ transaction });
+ if (
+ user.twofa &&
+ (typeof twofa === "undefined" ||
+ !authenticator.check(twofa, user.otpsecret))
+ ) {
+ return res.code(401).send("2fa required");
+ }
- 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
- },
- { transaction }
+ l(
+ "login",
+ username,
+ req.headers["x-forwarded-for"] || req.socket.remoteAddress
);
- 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");
- }
-});
+ let payload = { username, id: uid };
+ let token = jwt.sign(payload, config.jwt);
+ res.cookie("token", token, { expires: new Date(Date.now() + 432000000) });
+ user = pick(user, whitelist);
+ res.send({ user, token });
+ } catch (e) {
+ console.log(e);
+ err("login error", e.message, req.socket.remoteAddress);
+ res.code(401).send({});
+ }
+ },
-app.post("/checkRedeemCode", auth, async function(req, res) {
- const { redeemcode } = req.body;
+ async logout(req, res) {
+ let { subscription } = req.body;
+ if (!subscription) return res.send({});
- const payment = await db.Payment.findOne({ where: { redeemcode } });
- res.send(payment);
-});
+ const { username } = req.user;
-app.post("/password", auth, async function(req, res) {
- const { user } = req;
- const { password } = req.body;
+ if (username) {
+ l("logging out", username);
+ let i = req.user.subscriptions.findIndex(
+ s => JSON.stringify(s) === subscription
+ );
+ if (i > -1) {
+ req.user.subscriptions.splice(i, 1);
+ }
+ await req.user.save();
+ Object.keys(logins).map(
+ k => logins[k]["username"] === username && delete logins[k]
+ );
+ }
- if (!user.password) return res.send(true);
- res.send(await bcrypt.compare(password, user.password));
-});
+ res.send({});
+ },
-app.get("/isInternal", auth, async function(req, res) {
- let { user } = req;
- let { address } = req.query;
- if (!address) throw new Error("Address not provided");
+ async subscribe({ body, user }, res) {
+ let { subscriptions } = user;
+ let { subscription } = body;
+ if (!subscriptions) subscriptions = [];
+ if (
+ !subscriptions.find(
+ s => JSON.stringify(s) === JSON.stringify(subscription)
+ )
+ )
+ subscriptions.push(subscription);
+ user.subscriptions = subscriptions;
+ l("subscribing", user.username);
+ await user.save();
+ res.sendStatus(201);
+ },
- let invoice = await db.Invoice.findOne({
- where: { address },
- include: {
- attributes: ["username"],
- model: db.User,
- as: "user"
- }
- });
+ async password({ body: { password }, user }, res) {
+ if (!user.password) return res.send(true);
+ res.send(await bcrypt.compare(password, user.password));
+ },
- if (invoice) {
- let info;
+ async otpsecret(req, res) {
try {
- info = await bc.getAddressInfo(address);
+ await requirePin(req);
+ let { otpsecret, username } = req.user;
+ res.send({ secret: otpsecret, username });
} catch (e) {
- info = await lq.getAddressInfo(address);
+ res.code(500).send(e.message);
}
- if (info.ismine) {
- emit(user.username, "to", invoice.user);
- return res.send(true);
- }
- }
+ },
- res.send(false);
-});
+ async contacts({ user: { id } }, res) {
+ let i = (await g(`${id}:cindex`)) || 0;
+ let payments = (await db.lRange(`${id}:payments`, i, -1)) || [];
+ await db.incrBy(`${id}:cindex`, payments.length);
-app.get("/invoices", auth, async function(req, res) {
- let invoices = await db.Invoice.findAll({
- where: { user_id: req.user.id }
- });
- res.send(invoices);
-});
+ let contacts = (await g(`${id}:contacts`)) || [];
-app.post("/signMessage", auth, async function(req, res) {
- let { address, message } = req.body;
+ for (let { ref } of (
+ await Promise.all(payments.map(async id => await g(`payment:${id}`)))
+ ).filter(p => p.type === types.internal && p.ref)) {
+ !~contacts.findIndex(({ id }) => id === ref) &&
+ contacts.push(await g(`user:${ref}`));
+ }
- let invoices = await db.Invoice.findAll({
- where: { user_id: req.user.id }
- });
+ await s(`${id}:contacts`, contacts);
- 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.send(contacts);
+ },
- res.code(500).send("Address not found for user");
-});
+ async del({ params: { username }, headers: { authorization } }, res) {
+ if (!(authorization && authorization.includes(config.admin)))
+ return res.code(401).send("unauthorized");
-app.post("/otpsecret", auth, async function(req, res) {
- try {
- await requirePin(req);
- let { otpsecret } = req.user;
- res.send({ secret: otpsecret });
- } catch (e) {
- res.code(500).send(e.message);
- }
-});
+ let { id, pubkey } = await g(`user:${await g(`user:${username}`)}`);
+ let invoices = await db.lRange(`${id}:invoices`, 0, -1);
+ let payments = await db.lRange(`${id}:payments`, 0, -1);
-app.get("/contacts", auth, async function(req, res) {
- try {
- let contacts = await db.Payment.findAll({
- where: {
- user_id: req.user.id,
- with_id: { [Sequelize.Op.ne]: null }
- },
- include: {
- attributes: ["username", "profile"],
- model: db.User,
- as: "with"
- },
- attributes: [
- [Sequelize.fn("max", Sequelize.col("payments_model.createdAt")), "last"]
- ],
- group: ["with_id"],
- order: [[{ model: db.User, as: "with" }, "username", "ASC"]]
- });
+ for (let { id } of invoices) db.del(`invoice:${id}`);
+ for (let { id } of payments) db.del(`payment:${id}`);
+ db.del(`user:${username}`);
+ db.del(`user:${id}`);
+ db.del(`user:${pubkey}`);
- res.send(
- contacts.map(c => {
- c = c.get({ plain: true });
- return { ...c.with, last: c.last };
- })
- );
- } catch (e) {
- console.log(e);
- res.code(500).send(e.message);
+ res.send({});
}
-});
+};