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 /routes | |
| parent | b67ca07bac3a0cb78f9b3c19a783c3253f06c29f (diff) | |
| download | coinos-server-44b5d463d8e32d9dd400ebb834ed2a877d93a639.tar.gz coinos-server-44b5d463d8e32d9dd400ebb834ed2a877d93a639.zip | |
calculate fee from inputs and outputs
Diffstat (limited to 'routes')
| -rw-r--r-- | routes/payments.js | 218 | ||||
| -rw-r--r-- | routes/users.js | 4 |
2 files changed, 57 insertions, 165 deletions
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; |
