aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdrien Hopkins <[email protected]>2022-06-16 14:48:27 -0500
committerAdrien Hopkins <[email protected]>2022-06-16 14:48:27 -0500
commitc66b9c6b5be4b1b524ee2675dd1f65f425d72ee9 (patch)
treeeba3a920bd31da13e76afea2835c1f6e515d1d11
parentffbd27b2e9ddcb52a37febc0855e16b4fe09dbdc (diff)
parent67e2ec07cf90cfbd1b5c1cbed18c4dffe38ea4aa (diff)
downloadcoinos-server-c66b9c6b5be4b1b524ee2675dd1f65f425d72ee9.tar.gz
coinos-server-c66b9c6b5be4b1b524ee2675dd1f65f425d72ee9.zip
Merge branch 'feature-withdrawal-fee'
-rw-r--r--routes/bitcoin/conversionFee.js12
-rw-r--r--routes/bitcoin/receive.js6
-rw-r--r--routes/bitcoin/send.js43
-rw-r--r--routes/lightning/conversionFee.js12
-rw-r--r--routes/lightning/receive.js4
-rw-r--r--routes/liquid/conversionFee.js12
-rw-r--r--routes/liquid/receive.js5
-rw-r--r--routes/liquid/send.js46
-rw-r--r--routes/lnurl.js51
9 files changed, 137 insertions, 54 deletions
diff --git a/routes/bitcoin/conversionFee.js b/routes/bitcoin/conversionFee.js
new file mode 100644
index 00000000..9df60c2a
--- /dev/null
+++ b/routes/bitcoin/conversionFee.js
@@ -0,0 +1,12 @@
+// 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.01;
+const conversionFeeReceiver = "coinosfees"; // account that receives the fees
+
+/**
+ * Returns the fee you have to pay for withdrawing 'amount' bitcoin.
+ */
+function computeConversionFee(amount) {
+ return Math.floor(amount * conversionFeeMultiplier);
+}
diff --git a/routes/bitcoin/receive.js b/routes/bitcoin/receive.js
index 5bdbcb72..45dadbbe 100644
--- a/routes/bitcoin/receive.js
+++ b/routes/bitcoin/receive.js
@@ -21,8 +21,7 @@ const network =
const queue = {};
const seen = [];
-// used to calculate how many credits to give
-const withdrawalFeeMultiplier = 0.01;
+const { computeConversionFee } = require('./conversionFee.js');
zmqRawTx.on("message", async (topic, message, sequence) => {
const hex = message.toString("hex");
@@ -195,7 +194,8 @@ setInterval(async () => {
{ pending: Math.min(account.pending, total) },
{ transaction }
);
- await account.increment({ fee_credits: Math.floor(total * withdrawalFeeMultiplier) }, { 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 p.save({ transaction });
diff --git a/routes/bitcoin/send.js b/routes/bitcoin/send.js
index a9c95b31..634b223b 100644
--- a/routes/bitcoin/send.js
+++ b/routes/bitcoin/send.js
@@ -1,6 +1,5 @@
const btc = config.liquid.btcasset;
-const withdrawalFeeMultiplier = 0.01; // 1% withdrawal fee
-const withdrawalFeeReceiver = "coinosfees"; // account that receives the fees
+let { computeConversionFee, conversionFeeReceiver } = require('./conversionFee.js');
module.exports = ah(async (req, res) => {
let { user } = req;
@@ -31,10 +30,10 @@ module.exports = ah(async (req, res) => {
total = total - change + fee;
let amount = total - fee;
- // get withdrawal fee
- // 'total' refers to the total before the withdrawal fee
+ // get conversion fee
+ // 'total' refers to the total before the conversion fee
// (i.e. the total bitcoin that leaves this server)
- let withdrawalFee = Math.floor(amount * withdrawalFeeMultiplier);
+ let conversionFee = computeConversionFee(amount);
try {
// withdraw bitcoin
@@ -59,28 +58,28 @@ module.exports = ah(async (req, res) => {
});
}
+ // 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;
+ }
+
if (total > account.balance) {
l.error("amount exceeds balance", amount, fee, account.balance);
throw new Error("low balance");
- } else if (total + withdrawalFee > account.balance) {
- l.error("total (after withdrawal fee) exceeds balance", amount, fee, account.balance);
- throw new Error("low balance (after withdrawal fee)");
- }
-
- // use user's credits to reduce fee, if available
- let withdrawalFeeDeduction = Math.min(account.fee_credits, withdrawalFee);
- if (withdrawalFeeDeduction) {
- await account.decrement({ fee_credits: withdrawalFeeDeduction }, { transaction });
- await account.reload({ transaction });
- withdrawalFee -= withdrawalFeeDeduction;
+ } else if (total + conversionFee > account.balance) {
+ l.error("total (after conversion fee) exceeds balance", amount, fee, account.balance);
+ throw new Error("low balance (after conversion fee)");
}
- await account.decrement({ balance: (total + withdrawalFee) }, { transaction });
+ await account.decrement({ balance: (total + conversionFee) }, { transaction });
await account.reload({ transaction });
let receiverAccount = await db.Account.findOne({
where: {
- "$user.username$": withdrawalFeeReceiver
+ "$user.username$": conversionFeeReceiver
},
include: [
{
@@ -93,13 +92,13 @@ module.exports = ah(async (req, res) => {
});
let fee_payment_id = null;
- if (withdrawalFee) {
- await receiverAccount.increment({ balance: withdrawalFee }, { transaction });
+ if (conversionFee) {
+ await receiverAccount.increment({ balance: conversionFee }, { transaction });
await receiverAccount.reload({ transaction });
let fee_payment = await db.Payment.create({
- amount: withdrawalFee,
+ amount: conversionFee,
fee: 0,
- memo: "Bitcoin withdrawal fee",
+ memo: "Bitcoin conversion fee",
account_id: receiverAccount.id,
user_id: receiverAccount.user_id,
rate: app.get("rates")[receiverAccount.user.currency],
diff --git a/routes/lightning/conversionFee.js b/routes/lightning/conversionFee.js
new file mode 100644
index 00000000..7359bb3f
--- /dev/null
+++ b/routes/lightning/conversionFee.js
@@ -0,0 +1,12 @@
+// 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.01;
+const conversionFeeReceiver = "coinosfees"; // account that receives the fees
+
+/**
+ * Returns the fee you have to pay for withdrawing 'amount' lightning BTC.
+ */
+function computeConversionFee(amount) {
+ return Math.floor(amount * conversionFeeMultiplier);
+}
diff --git a/routes/lightning/receive.js b/routes/lightning/receive.js
index 6723d577..6d10eb9b 100644
--- a/routes/lightning/receive.js
+++ b/routes/lightning/receive.js
@@ -1,3 +1,5 @@
+const { computeConversionFee } = require('./conversionFee.js');
+
const handlePayment = async (msg) => {
l.info("incoming lightning payment", msg.value, msg.payment_request, msg.settled);
if (!msg.settled) return;
@@ -60,6 +62,8 @@ const handlePayment = async (msg) => {
invoice.received += total;
await account.increment({ balance: 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.save({ transaction });
await payment.save({ transaction });
diff --git a/routes/liquid/conversionFee.js b/routes/liquid/conversionFee.js
new file mode 100644
index 00000000..d365185b
--- /dev/null
+++ b/routes/liquid/conversionFee.js
@@ -0,0 +1,12 @@
+// 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.01;
+const conversionFeeReceiver = "coinosfees"; // account that receives the fees
+
+/**
+ * Returns the fee you have to pay for withdrawing 'amount' L-BTC.
+ */
+function computeConversionFee(amount) {
+ return Math.floor(amount * conversionFeeMultiplier);
+}
diff --git a/routes/liquid/receive.js b/routes/liquid/receive.js
index 9650e5b7..2bd86805 100644
--- a/routes/liquid/receive.js
+++ b/routes/liquid/receive.js
@@ -13,8 +13,7 @@ const network =
config.liquid.network === "mainnet" ? "liquid" : config.liquid.network
];
-// used to calculate how many credits to give
-const withdrawalFeeMultiplier = 0.01;
+const { computeConversionFee } = require('./conversionFee.js');
const getAccount = async (params, transaction) => {
let account = await db.Account.findOne({
@@ -344,7 +343,7 @@ setInterval(async () => {
{ pending: Math.min(p.account.pending, total) },
{ transaction }
);
- await p.account.increment({ fee_credits: Math.floor(total * withdrawalFeeMultiplier) }, { transaction });
+ await p.account.increment({ liquid_credits: computeConversionFee(total) }, { transaction });
await p.account.reload({ transaction });
await p.save({ transaction });
diff --git a/routes/liquid/send.js b/routes/liquid/send.js
index fcb05942..254972ad 100644
--- a/routes/liquid/send.js
+++ b/routes/liquid/send.js
@@ -1,8 +1,7 @@
const btc = config.liquid.btcasset;
const lcad = config.liquid.cadasset;
const { Transaction } = require("liquidjs-lib");
-const withdrawalFeeMultiplier = 0.01; // 1% withdrawal fee
-const withdrawalFeeReceiver = "coinosfees"; // 1% withdrawal fee
+const { computeConversionFee, conversionFeeReceiver } = require('./conversionFee.js');
sendLiquid = async ({ asset, amount, user, address, memo, tx, limit }) => {
try {
@@ -84,10 +83,10 @@ sendLiquid = async ({ asset, amount, user, address, memo, tx, limit }) => {
total += fee - covered;
}
- // get withdrawal fee
- // 'total' refers to the total before the withdrawal fee
+ // get conversion fee
+ // 'total' refers to the total before the conversion fee
// (i.e. the total liquid bitcoin that leaves this server)
- var withdrawalFee = Math.floor(amount * withdrawalFeeMultiplier);
+ var conversionFee = computeConversionFee(amount);
if (limit && total > limit + fee)
throw new Error("Tx amount exceeds authorized amount");
@@ -106,6 +105,14 @@ sendLiquid = async ({ asset, amount, user, address, memo, tx, limit }) => {
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 (total > account.balance) {
l.warn("amount exceeds balance", {
total,
@@ -117,35 +124,26 @@ sendLiquid = async ({ asset, amount, user, address, memo, tx, limit }) => {
account.ticker === "BTC" ? "SAT" : account.ticker
}, have ${account.balance}`
);
- } else if (total + withdrawalFee > account.balance) {
- l.warn("amount plus withdrawal fee exceeds balance", {
+ } else if (total + conversionFee > account.balance) {
+ l.warn("amount plus conversion fee exceeds balance", {
total,
fee,
- withdrawalFee,
+ conversionFee,
balance: account.balance
});
throw new Error(
- `Insufficient funds, need ${total + withdrawalFee} ${
+ `Insufficient funds, need ${total + conversionFee} ${
account.ticker === "BTC" ? "SAT" : account.ticker
}, have ${account.balance}`
);
}
- // use user's credits to reduce fee, if available
- console.log(account.fee_credits);
- let withdrawalFeeDeduction = Math.min(account.fee_credits, withdrawalFee);
- if (withdrawalFeeDeduction) {
- await account.decrement({ fee_credits: withdrawalFeeDeduction }, { transaction });
- await account.reload({ transaction });
- withdrawalFee -= withdrawalFeeDeduction;
- }
-
- await account.decrement({ balance: (total + withdrawalFee)}, { transaction });
+ await account.decrement({ balance: (total + conversionFee)}, { transaction });
await account.reload({ transaction });
let receiverAccount = await db.Account.findOne({
where: {
- "$user.username$": withdrawalFeeReceiver
+ "$user.username$": conversionFeeReceiver
},
include: [
{
@@ -158,13 +156,13 @@ sendLiquid = async ({ asset, amount, user, address, memo, tx, limit }) => {
});
let fee_payment_id = null;
- if (withdrawalFee) {
- await receiverAccount.increment({ balance: withdrawalFee }, { transaction });
+ if (conversionFee) {
+ await receiverAccount.increment({ balance: conversionFee }, { transaction });
await receiverAccount.reload({ transaction });
let fee_payment = {
- amount: withdrawalFee,
+ amount: conversionFee,
fee: 0,
- memo: "Liquid withdrawal fee",
+ memo: "Liquid conversion fee",
account_id: receiverAccount.id,
user_id: receiverAccount.user_id,
rate: app.get("rates")[receiverAccount.user.currency],
diff --git a/routes/lnurl.js b/routes/lnurl.js
index 0466e143..93d939f9 100644
--- a/routes/lnurl.js
+++ b/routes/lnurl.js
@@ -12,6 +12,8 @@ withdrawals = persist("data/withdrawals.json");
lnurlServer = lnurl.createServer(config.lnurl);
+let { computeConversionFee, conversionFeeReceiver } = require('lightning/conversionFee.js');
+
var optionalAuth = function(req, res, next) {
passport.authenticate("jwt", { session: false }, function(err, user, info) {
req.user = user;
@@ -391,6 +393,7 @@ lnurlServer.bindToHook(
try {
let decoded = await lnp.decodePayReq({ pay_req: pr });
let amount = decoded.num_satoshis;
+ let conversionFee = computeConversionFee(amount);
await db.transaction(async transaction => {
let account = await db.Account.findOne({
@@ -402,8 +405,51 @@ lnurlServer.bindToHook(
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: app.get("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(
@@ -415,7 +461,8 @@ lnurlServer.bindToHook(
rate: app.get("rates")[user.currency],
currency: user.currency,
confirmed: true,
- network: "lightning"
+ network: "lightning",
+ fee_payment_id: fee_payment_id
},
{ transaction }
);
@@ -447,7 +494,7 @@ lnurlServer.bindToHook(
}
}, 1000);
- await account.decrement({ balance: amount }, { transaction });
+ await account.decrement({ balance: (amount + conversionFee) }, { transaction });
await account.reload({ transaction });
let p = payment.get({ plain: true });