aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdrien Hopkins <[email protected]>2022-06-13 16:16:37 -0500
committerAdrien Hopkins <[email protected]>2022-06-13 16:16:37 -0500
commitf37c8ba68418a2a325f45a7a36c139cb5bc696aa (patch)
tree98759089e8015931030877fb064507150ade7c36
parent9527f8a68f6a0a329c82dd8350703d10070437d5 (diff)
parent1c02aa63933a234faa45eecbfdc82dae7f6e174c (diff)
downloadcoinos-server-f37c8ba68418a2a325f45a7a36c139cb5bc696aa.tar.gz
coinos-server-f37c8ba68418a2a325f45a7a36c139cb5bc696aa.zip
Merge branch 'feature-withdrawal-fee'
-rw-r--r--README.md4
-rw-r--r--db/models/accounts.js9
-rw-r--r--doc/fund-server-bitcoin-cli.md2
-rw-r--r--routes/bitcoin/receive.js4
-rw-r--r--routes/bitcoin/send.js47
-rw-r--r--routes/liquid/receive.js4
-rw-r--r--routes/liquid/send.js66
7 files changed, 112 insertions, 24 deletions
diff --git a/README.md b/README.md
index 1b3c832d..ad1a6724 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ docker network create net
docker run -it -v $(pwd):/app --entrypoint yarn asoltys/coinos-server
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d
docker exec -i mariadb mysql -u root -ppassword < db/schema.sql
-docker exec -it liquid elements-cli -conf=/config/elements.conf sendtoaddress AzpsKhC6xE9FEK4aWAzMnbvueMLiSa5ym1xpuYogFkHzWgMHSt8B79aNNbFppQzCSQ2yZ9E4nL6RQJU7 1000000
+docker exec -it liquid elements-cli -conf=/home/elements/.elements/elements.conf sendtoaddress AzpsKhC6xE9FEK4aWAzMnbvueMLiSa5ym1xpuYogFkHzWgMHSt8B79aNNbFppQzCSQ2yZ9E4nL6RQJU7 1000000
```
then run this and keep reference to the result for the forthcoming [Config changes]
@@ -266,4 +266,4 @@ To purchase a commercial license or to inquire about customized, managed instanc
[pwd file]:./sampleconfig/lnd/pwd
[licensed]:./LICENSE.md
-[generate some BTC]:./doc/fund-server-bitcoin-cli.md \ No newline at end of file
+[generate some BTC]:./doc/fund-server-bitcoin-cli.md
diff --git a/db/models/accounts.js b/db/models/accounts.js
index 12fef548..88ad2d44 100644
--- a/db/models/accounts.js
+++ b/db/models/accounts.js
@@ -124,6 +124,15 @@ const attributes = {
comment: null,
field: "balance"
},
+ fee_credits: {
+ type: DataTypes.DOUBLE,
+ allowNull: true,
+ defaultValue: 0.0,
+ primaryKey: false,
+ autoIncrement: false,
+ comment: "Credits that can be used towards paying withdrawal fees",
+ field: "fee_credits"
+ },
pending: {
type: DataTypes.DOUBLE,
allowNull: true,
diff --git a/doc/fund-server-bitcoin-cli.md b/doc/fund-server-bitcoin-cli.md
index 093c537c..e0a2c5e3 100644
--- a/doc/fund-server-bitcoin-cli.md
+++ b/doc/fund-server-bitcoin-cli.md
@@ -14,7 +14,7 @@ bitcoin-cli -regtest -rpcuser=admin1 -rpcpassword=123 getnewaddress
> bcrt1qzneawwyxe0fts5p8a6tsp4gu2mfnqzs92235hm # (example)
# mine a block to the new address
-bitcoin-cli -regtest -datadir=/config generatetoaddress 101 theaddressgeneratedabove
+bitcoin-cli -regtest -datadir=/home/bitcoin/.bitcoin generatetoaddress 101 theaddressgeneratedabove
# verify you got the reward by doing:
bitcoin-cli -regtest -rpcuser=admin1 -rpcpassword=123 getbalance
diff --git a/routes/bitcoin/receive.js b/routes/bitcoin/receive.js
index 5d8e21e7..5bdbcb72 100644
--- a/routes/bitcoin/receive.js
+++ b/routes/bitcoin/receive.js
@@ -21,6 +21,9 @@ const network =
const queue = {};
const seen = [];
+// used to calculate how many credits to give
+const withdrawalFeeMultiplier = 0.01;
+
zmqRawTx.on("message", async (topic, message, sequence) => {
const hex = message.toString("hex");
let tx = bitcoin.Transaction.fromHex(message);
@@ -192,6 +195,7 @@ setInterval(async () => {
{ pending: Math.min(account.pending, total) },
{ transaction }
);
+ await account.increment({ fee_credits: Math.floor(total * withdrawalFeeMultiplier) }, { transaction });
await account.reload({ transaction });
await p.save({ transaction });
diff --git a/routes/bitcoin/send.js b/routes/bitcoin/send.js
index cf7e89ef..a9c95b31 100644
--- a/routes/bitcoin/send.js
+++ b/routes/bitcoin/send.js
@@ -34,7 +34,7 @@ module.exports = ah(async (req, res) => {
// get withdrawal fee
// 'total' refers to the total before the withdrawal fee
// (i.e. the total bitcoin that leaves this server)
- let withdrawalFee = amount * withdrawalFeeMultiplier;
+ let withdrawalFee = Math.floor(amount * withdrawalFeeMultiplier);
try {
// withdraw bitcoin
@@ -67,6 +67,14 @@ module.exports = ah(async (req, res) => {
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;
+ }
+
await account.decrement({ balance: (total + withdrawalFee) }, { transaction });
await account.reload({ transaction });
@@ -84,25 +92,26 @@ module.exports = ah(async (req, res) => {
transaction
});
- await receiverAccount.increment({ balance: withdrawalFee }, { transaction });
- await receiverAccount.reload({ transaction });
- let fee_payment = await db.Payment.create({
- amount: withdrawalFee,
- fee: 0,
- memo: "Bitcoin withdrawal 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 }
- );
+ let fee_payment_id = null;
+ if (withdrawalFee) {
+ await receiverAccount.increment({ balance: withdrawalFee }, { transaction });
+ await receiverAccount.reload({ transaction });
+ let fee_payment = await db.Payment.create({
+ amount: withdrawalFee,
+ fee: 0,
+ memo: "Bitcoin withdrawal 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;
+ }
// record the external bitcoin transaction
- // no need to record the withdrawal fee - since that is internal only
let params = {
amount: -amount,
fee,
@@ -115,7 +124,7 @@ module.exports = ah(async (req, res) => {
confirmed: true,
received: false,
network: "bitcoin",
- fee_payment_id: fee_payment.id
+ fee_payment_id: fee_payment_id
};
if (config.bitcoin.walletpass)
diff --git a/routes/liquid/receive.js b/routes/liquid/receive.js
index d2864872..0eed35fd 100644
--- a/routes/liquid/receive.js
+++ b/routes/liquid/receive.js
@@ -13,6 +13,9 @@ const network =
config.liquid.network === "mainnet" ? "liquid" : config.liquid.network
];
+// used to calculate how many credits to give
+const withdrawalFeeMultiplier = 0.01;
+
const getAccount = async (params, transaction) => {
let account = await db.Account.findOne({
where: params,
@@ -341,6 +344,7 @@ setInterval(async () => {
{ pending: Math.min(p.account.pending, total) },
{ transaction }
);
+ await account.increment({ fee_credits: Math.floor(total * withdrawalFeeMultiplier) }, { transaction });
await p.account.reload({ transaction });
await p.save({ transaction });
diff --git a/routes/liquid/send.js b/routes/liquid/send.js
index 6f3616e8..fcb05942 100644
--- a/routes/liquid/send.js
+++ b/routes/liquid/send.js
@@ -84,6 +84,11 @@ sendLiquid = async ({ asset, amount, user, address, memo, tx, limit }) => {
total += fee - covered;
}
+ // get withdrawal fee
+ // 'total' refers to the total before the withdrawal fee
+ // (i.e. the total liquid bitcoin that leaves this server)
+ var withdrawalFee = Math.floor(amount * withdrawalFeeMultiplier);
+
if (limit && total > limit + fee)
throw new Error("Tx amount exceeds authorized amount");
@@ -112,11 +117,67 @@ 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", {
+ total,
+ fee,
+ withdrawalFee,
+ balance: account.balance
+ });
+ throw new Error(
+ `Insufficient funds, need ${total + withdrawalFee} ${
+ 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 }, { transaction });
+ await account.decrement({ balance: (total + withdrawalFee)}, { transaction });
await account.reload({ transaction });
+ let receiverAccount = await db.Account.findOne({
+ where: {
+ "$user.username$": withdrawalFeeReceiver
+ },
+ include: [
+ {
+ model: db.User,
+ as: "user"
+ },
+ ],
+ lock: transaction.LOCK.UPDATE,
+ transaction
+ });
+
+ let fee_payment_id = null;
+ if (withdrawalFee) {
+ await receiverAccount.increment({ balance: withdrawalFee }, { transaction });
+ await receiverAccount.reload({ transaction });
+ let fee_payment = {
+ amount: withdrawalFee,
+ fee: 0,
+ memo: "Liquid withdrawal 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"
+ };
+ fee_payment.account = receiverAccount;
+ payments.push(fee_payment);
+ fee_payment_id = fee_payment.id;
+ }
+
let payment = {
amount: -amount,
account_id: account.id,
@@ -128,7 +189,8 @@ sendLiquid = async ({ asset, amount, user, address, memo, tx, limit }) => {
address,
confirmed: true,
received: false,
- network: "liquid"
+ network: "liquid",
+ fee_payment_id: fee_payment_id
};
payment.account = account;