diff options
| author | Adam Soltys <[email protected]> | 2023-12-20 14:39:05 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2023-12-20 14:39:08 -0800 |
| commit | 44b5d463d8e32d9dd400ebb834ed2a877d93a639 (patch) | |
| tree | 3e52a95fba04dc96dd1d1ab0e79e2d8f03564781 | |
| parent | b67ca07bac3a0cb78f9b3c19a783c3253f06c29f (diff) | |
| download | coinos-server-44b5d463d8e32d9dd400ebb834ed2a877d93a639.tar.gz coinos-server-44b5d463d8e32d9dd400ebb834ed2a877d93a639.zip | |
calculate fee from inputs and outputs
| -rw-r--r-- | index.js | 4 | ||||
| -rw-r--r-- | lib/payments.js | 16 | ||||
| -rw-r--r-- | routes/payments.js | 218 | ||||
| -rw-r--r-- | routes/users.js | 4 |
4 files changed, 68 insertions, 174 deletions
@@ -58,7 +58,6 @@ app.post("/parse", payments.parse); app.get("/fund/:name", payments.fund); app.get("/fund/:name/withdraw", payments.withdraw); app.post("/take", auth, payments.take); -app.post("/buy", auth, payments.buy); app.post("/print", auth, payments.print); app.post("/send/:lnaddress/:amount", auth, payments.lnaddress); @@ -68,13 +67,11 @@ app.get("/lnurlp/:username", lnurl.lnurlp); app.get("/lnurl/:id", lnurl.lnurl); app.post("/freeze", payments.freeze); -app.post("/fix", auth, payments.fix); app.post("/bitcoin", payments.bitcoin); app.post("/bitcoin/fee", auth, payments.fee); app.post("/bitcoin/send", auth, payments.send); app.get("/users", auth, users.list); -app.get("/fix", auth, users.fix); app.get("/me", auth, users.me); app.get("/users/:key", users.get); app.post("/register", users.create); @@ -102,7 +99,6 @@ app.get("/requests", auth, requests.list); app.post("/requests", auth, requests.create); app.post("/requests/delete", auth, requests.destroy); -app.post("/proxy", payments.proxy); app.post("/shopify/:id", shopify); let host = process.env.HOST || "0.0.0.0"; diff --git a/lib/payments.js b/lib/payments.js index 20429864..d257ed77 100644 --- a/lib/payments.js +++ b/lib/payments.js @@ -25,7 +25,7 @@ export const types = { classic: "classic", }; -export let debit = async ( +export let debit = async ({ hash, amount, fee = 0, @@ -33,12 +33,11 @@ export let debit = async ( user, type = types.internal, id = v4(), -) => { + rate = store.rates[currency] +}) => { let ref; let { id: uid, currency, username } = user; - let rate = store.rates[currency]; - let iid = await g(`invoice:${hash}`); if (iid && iid.hash) iid = iid.hash; let invoice = await g(`invoice:${iid}`); @@ -259,7 +258,14 @@ export let payLightning = async (user, payreq, amount, maxfee, memo) => { if (pays.find((p) => p.status === "pending")) fail("Payment is already underway"); - p = await debit(payreq, total, maxfee, memo, user, types.lightning); + p = await debit({ + hash: payreq, + amount: total, + fee: maxfee, + memo, + user, + type: types.lightning, + }); let check = async () => { try { diff --git a/routes/payments.js b/routes/payments.js index 2b332221..328e4d80 100644 --- a/routes/payments.js +++ b/routes/payments.js @@ -55,7 +55,13 @@ export default { if (username && username.endsWith("@classic")) { let { username: source } = user; username = username.replace("@classic", ""); - p = await debit(hash, amount, 0, username, user, types.classic); + p = await debit({ + hash, + amount, + memo: username, + user, + type: types.classic, + }); await got .post(`${config.classic}/admin/credit`, { @@ -69,10 +75,10 @@ export default { if (!p) { if (hash) { - p = await debit(hash, amount, 0, memo, user); + p = await debit({ hash, amount, memo, user }); await credit(hash, amount, memo, user.id); } else if (fund) { - p = await debit(hash, amount, 0, fund, user, types.fund); + p = await debit({ hash, amount, memo: fund, user, type: types.fund }); await db.incrBy(`fund:${fund}`, amount); await db.lPush(`fund:${fund}:payments`, p.id); l("funded fund", fund); @@ -251,45 +257,56 @@ export default { } }, - async fee({ body: { amount, address, feeRate, subtract }, user }, res) { + async fee({ body: { amount, address, feeRate: fee_rate }, user }, res) { try { + amount = parseInt(amount); + + let replaceable = true; let ourfee = Math.round(amount * config.fee); let credit = await g(`credit:bitcoin:${user.id}`); let covered = Math.min(credit, ourfee) || 0; ourfee -= covered; - if (subtract) amount -= ourfee; - let outs = [{ [address]: btc(amount) }]; let raw = await bc.createRawTransaction([], outs, 0, replaceable); let tx = await bc.fundRawTransaction(raw, { - replaceable: true, + fee_rate, + replaceable, + subtractFeeFromOutputs: [], }); let fee = sats(tx.fee); + let dust = 547; + + let balance = await g(`balance:${user.id}`); + + if (amount + fee + ourfee > balance) { + if (amount <= fee + ourfee + dust) + return bail(res, "insufficient funds"); + + outs = [{ [address]: btc(amount - ourfee) }]; + raw = await bc.createRawTransaction([], outs, 0, replaceable); - if (amount + fee + ourfee > (await g(`balance:${user.id}`))) { tx = await bc.fundRawTransaction(raw, { - replaceable: true, + fee_rate, + replaceable, subtractFeeFromOutputs: [0], }); + + fee = sats(tx.fee); } - res.send({ feeRate, ourfee, fee, tx }); + res.send({ feeRate: fee_rate, ourfee, fee, hex: tx.hex }); } catch (e) { warn("problem estimating fee", e.message, user.username, amount, address); bail(res, "problem estimating fee"); } }, - async send(req, res) { + async send({ body: { address, memo, hex, rate, pin }, user }, res) { try { - await requirePin(req); - - let { user } = req; - let { address, memo, tx, subtract } = req.body; - let { hex, fee } = tx; + await requirePin({ body: { pin }, user }); if (config.bitcoin.walletpass) await bc.walletPassphrase(config.bitcoin.walletpass, 300); @@ -299,31 +316,42 @@ export default { 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 tx = await bc.decodeRawTransaction(hex); let { txid } = tx; let total = 0; let change = 0; + let totalIn = 0; + for await (let { txid, vout } of tx.vin) { + let hex = await bc.getRawTransaction(txid); + let tx = await bc.decodeRawTransaction(hex); + totalIn += sats(tx.vout[vout].value); + } + for (let { scriptPubKey: { address }, value, } of tx.vout) { total += sats(value); - let iid = await g(`invoice:${address}`); - if ((await bc.getAddressInfo(address)).ismine && !iid) - change += sats(value); - } + if (await g(`invoice:${address}`)) + fail("Cannot send to internal address"); - total = total - change + fee; - let amount = total - fee; + if ((await bc.getAddressInfo(address)).ismine) change += sats(value); + } - if (change && !amount) fail("Cannot send to unregistered coinos address"); + let fee = totalIn - total; + let amount = total - change; - await debit(txid, amount, fee, null, user, types.bitcoin, txid); + await debit({ + hash: txid, + amount, + fee, + rate, + user, + type: types.bitcoin, + id: txid, + }); await bc.sendRawTransaction(hex); res.send({ txid }); @@ -333,138 +361,6 @@ export default { } }, - async buy({ body: { amount, number, year, month, cvc }, user }, res) { - if (!user.eligible) fail("not eligible"); - - let stripe = "https://api.stripe.com/v1"; - let { stripe: username } = config; - - let form = { - "card[number]": number, - "card[exp_month]": month, - "card[exp_year]": year, - "card[cvc]": cvc, - }; - - let { id: source } = await got - .post(`${stripe}/tokens`, { - form, - username, - }) - .json(); - - let currency = "CAD"; - form = { - amount, - currency, - source, - description: "starter coupon", - }; - - let r = await got - .post(`${stripe}/charges`, { - form, - username, - }) - .json(); - - let { status } = r; - if (status === "succeeded") { - let id = v4(); - let hash = r.id; - let memo = "stripe"; - await s(`invoice:${id}`, { - id, - hash: id, - uid: user.id, - received: 0, - }); - amount = sats(amount / store.rates[currency]); - let uid = await g("user:coinos"); - let coinos = await g(`user:${uid}`); - let p = await debit(hash, amount, 0, memo, coinos); - await credit(hash, amount, memo, coinos.id); - } else fail("Card payment failed"); - - res.send({ status }); - }, - - async proxy({ body: { method, params } }, res) { - try { - let whitelist = [ - "getblock", - "getblockhash", - "estimatesmartfee", - "echo", - "getblockchaininfo", - "getnetworkinfo", - ]; - - if (!whitelist.includes(method)) fail("unsupported method"); - - if (method === "estimatesmartfee" || method === "getblockhash") - params[0] = parseInt(params[0]); - - if (method === "getblock") params[1] = parseInt(params[1]); - - let result = await bc[method](...params); - - if (result.feerate) result.feerate = result.feerate.toFixed(8); - - res.send(result); - } catch (e) { - bail(res, e.message); - } - }, - - async fix({ body: { uid, username }, user }, res) { - try { - if (!user.admin) fail("unauthorized"); - - for await (let k of db.scanIterator({ MATCH: "balance:*" })) { - let bal = await g(k); - if (bal < 0) s(k, 0); - } - - for await (let k of db.scanIterator({ MATCH: "*:lastlen" })) { - await db.del(k); - } - - for await (let k of db.scanIterator({ MATCH: "user:*" })) { - let u = await g(k); - let id = k.split(":")[1]; - - if (typeof u === "string") { - id = u; - u = await g(`user:${u}`); - } - - if (!(u && u.username)) continue; - - let touched; - - console.log(u.username, u.profile, u.banner); - - if (u.profile) { - u.profile = `${u.id}-profile`; - touched = true; - } - - if (u.banner) { - u.banner = `${u.id}-banner`; - touched = true; - } - - if (touched) await s(`user:${u.id}`, u); - } - - res.send("ok"); - } catch (e) { - console.log(e); - bail(res, e.message); - } - }, - async freeze({ body: { secret } }, res) { try { if (secret !== config.adminpass) fail("unauthorized"); @@ -521,7 +417,7 @@ export default { let p = await payLightning(user, pr, amount, maxfee, memo); if (!p) { - p = await debit(pr, amount, 0, memo, user); + p = await debit({ hash: pr, amount, memo, user }); await credit(pr, amount, memo, user.id); } diff --git a/routes/users.js b/routes/users.js index 432908e9..a0796d63 100644 --- a/routes/users.js +++ b/routes/users.js @@ -399,10 +399,6 @@ export default { res.send({}); }, - async fix(req, res) { - res.send(req.headers); - }, - async reset({ body: { code, username, password }, user: u }, res) { let admin = u && u.admin; let id, user; |
