diff options
| author | Adam Soltys <[email protected]> | 2024-09-12 14:14:24 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2024-09-12 14:14:24 -0700 |
| commit | f76ffaf514d42f9d74698d9fddc2e98e54667578 (patch) | |
| tree | 62931abc9e901384844f97358cf660f6519151c9 | |
| parent | bc94288f562223ca3bd0dd969c68c6650adf6089 (diff) | |
| download | coinos-server-f76ffaf514d42f9d74698d9fddc2e98e54667578.tar.gz coinos-server-f76ffaf514d42f9d74698d9fddc2e98e54667578.zip | |
descriptor import
| -rwxr-xr-x | bun.lockb | bin | 207855 -> 207855 bytes | |||
| -rw-r--r-- | index.ts | 1 | ||||
| -rw-r--r-- | lib/payments.ts | 82 | ||||
| -rw-r--r-- | package.json | 2 | ||||
| -rw-r--r-- | routes/users.ts | 83 |
5 files changed, 116 insertions, 52 deletions
| Binary files differ @@ -79,6 +79,7 @@ app.post("/confirm", payments.confirm); app.post("/bitcoin/fee", auth, payments.fee); app.post("/bitcoin/send", auth, payments.send); +app.get("/account/:id", auth, users.account); app.get("/accounts", auth, users.accounts); app.post("/accounts", auth, users.createAccount); diff --git a/lib/payments.ts b/lib/payments.ts index 0f72b2c2..5f8f3e0a 100644 --- a/lib/payments.ts +++ b/lib/payments.ts @@ -30,6 +30,7 @@ let bc = rpc(config.bitcoin); let lq = rpc(config.liquid); let { URL } = process.env; +let dust = 547; export let types = { internal: "internal", @@ -41,6 +42,7 @@ export let types = { }; export let debit = async ({ + account = undefined, hash, amount, fee = 0, @@ -90,9 +92,11 @@ export let debit = async ({ ? Math.round((amount + fee + tip) * config.fee) : 0; + if (account) ourfee = 0; + ourfee = await db.debit( - `balance:${uid}`, - `credit:${type}:${uid}`, + `balance:${account || uid}`, + `credit:${type}:${account ? 0 : uid}`, amount || 0, tip || 0, fee || 0, @@ -104,6 +108,7 @@ export let debit = async ({ let id = v4(); let p = { id, + account, amount: -amount, fee, hash, @@ -123,7 +128,7 @@ export let debit = async ({ await s(`payment:${hash}`, id); await s(`payment:${id}`, p); - await db.lPush(`${uid}:payments`, id); + await db.lPush(`${account || uid}:payments`, id); l(user.username, "sent", type, amount); //emit(user.id, "payment", p); @@ -311,7 +316,7 @@ export let completePayment = async (p, user) => { } }; -let pay = async ({ amount, to, user }) => { +let pay = async ({ account, amount, to, user }) => { amount = parseInt(amount) || 0; let lnurl, pr; if (to.includes("@") && to.includes(".")) { @@ -335,7 +340,7 @@ let pay = async ({ amount, to, user }) => { return pr ? await sendLightning({ user, pr, amount, maxfee }) - : await sendOnchain({ amount, address: to, user, subtract: true }); + : await sendOnchain({ account, amount, address: to, user, subtract: true }); }; export let decode = async (hex) => { @@ -357,27 +362,29 @@ export let decode = async (hex) => { }; export let sendOnchain = async (params) => { - let { hex, rate, user } = params; + let { account, hex, rate, user, signed } = params; if (!hex) ({ hex } = await build(params)); let { tx, type } = await decode(hex); - let node = rpc(config[type]); + let node = rpc({ ...config[type], wallet: account }); let { txid } = tx; try { if (inflight[txid]) fail("payment in flight"); inflight[txid] = true; - if (config[type].walletpass) - await node.walletPassphrase(config[type].walletpass, 300); + if (!signed) { + if (config[type].walletpass) + await node.walletPassphrase(config[type].walletpass, 300); - let { hex: signed } = await node.signRawTransactionWithWallet( - type === types.liquid ? await node.blindRawTransaction(hex) : hex, - ); + ({ hex } = await node.signRawTransactionWithWallet( + type === types.liquid ? await node.blindRawTransaction(hex) : hex, + )); + } - ({ txid } = await node.decodeRawTransaction(signed)); + ({ txid } = await node.decodeRawTransaction(hex)); - let r = await node.testMempoolAccept([signed]); + let r = await node.testMempoolAccept([hex]); if (!r[0].allowed) fail("transaction rejected"); let total = 0; @@ -427,6 +434,7 @@ export let sendOnchain = async (params) => { let amount = total - change; let p = await debit({ + account, hash: txid, amount, fee, @@ -435,10 +443,10 @@ export let sendOnchain = async (params) => { type, }); - p.hex = signed; + p.hex = hex; await s(`payment:${p.id}`, p); - await node.sendRawTransaction(signed); + await node.sendRawTransaction(hex); delete inflight[txid]; return p; @@ -602,9 +610,9 @@ let getAddressType = async (a) => { } }; -export let build = async ({ amount, address, feeRate, user, subtract }) => { +export let build = async ({ account, amount, address, feeRate, subtract }) => { let type = await getAddressType(address); - let node = rpc(config[type]); + let node = rpc({ ...config[type], wallet: account }); amount = parseInt(amount); if (amount < 0) fail("invalid amount"); @@ -627,21 +635,28 @@ export let build = async ({ amount, address, feeRate, user, subtract }) => { let raw = await node.createRawTransaction([], outs, 0, replaceable); - let tx = await node.fundRawTransaction(raw, { - fee_rate: feeRate, - replaceable, - subtractFeeFromOutputs: [], - }); + let fee = 0; + let tx; - let fee = sats(tx.fee); - let dust = 547; + try { + tx = await node.fundRawTransaction(raw, { + fee_rate: feeRate, + replaceable, + subtractFeeFromOutputs: [], + }); + + fee = sats(tx.fee); + } catch (e) { + if (e.message.startsWith("Insufficient")) subtract = true; + } - let balance = await g(`balance:${user.id}`); + let balance = await g(`balance:${account}`); let ourfee = Math.round(amount * config.fee); - let credit = await g(`credit:${type}:${user.id}`); + let credit = await g(`credit:${type}:${account}`); let covered = Math.min(credit, ourfee); ourfee -= covered; + if (account) ourfee = 0; if (subtract || amount + fee + ourfee > balance) { if (amount <= fee + ourfee + dust) fail( @@ -660,7 +675,18 @@ export let build = async ({ amount, address, feeRate, user, subtract }) => { fee = sats(tx.fee); } - return { feeRate, ourfee, fee, fees, hex: tx.hex }; + let inputs = []; + let { vin } = await node.decodeRawTransaction(tx.hex); + + for (let { txid, vout } of vin) { + let nonWitnessUtxo = await node.getRawTransaction(txid); + let tx = await node.decodeRawTransaction(nonWitnessUtxo); + let { address } = tx.vout[vout].scriptPubKey; + let { hdkeypath: path } = await node.getAddressInfo(address); + inputs.push({ nonWitnessUtxo, path }); + } + + return { feeRate, ourfee, fee, fees, hex: tx.hex, inputs }; }; export let catchUp = async () => { diff --git a/package.json b/package.json index b4d5545e..0e2bf2d3 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "dependencies": { "@aws-sdk/client-ses": "^3.449.0", "@cashu/cashu-ts": "github:cashubtc/cashu-ts#development", - "@coinos/rpc": "^0.0.5", + "@coinos/rpc": "^0.0.6", "@fastify/cors": "^8.3.0", "@fastify/http-proxy": "^9.5.0", "@fastify/multipart": "^8.0.0", diff --git a/routes/users.ts b/routes/users.ts index a39bdbe3..eeb764ec 100644 --- a/routes/users.ts +++ b/routes/users.ts @@ -566,19 +566,6 @@ export default { } }, - async accounts(req, res) { - let { user } = req; - - let accounts = []; - for (let id of await db.lRange(`${user.id}:accounts`, 0, 1)) { - let account = await g(`account:${id}`); - account.balance = await g(`balance:${id}`); - accounts.push(account); - } - - res.send(accounts); - }, - async hidepay(req, res) { let { body: { username }, @@ -599,20 +586,70 @@ export default { res.send({}); }, - async createAccount(req, res) { - let { type, seed } = req.body; + async account(req, res) { + let { id } = req.params; + let account = await g(`account:${id}`); + account.balance = await g(`balance:${id}`); + res.send(account); + }, + + async accounts(req, res) { let { user } = req; - let id = v4(); - let account = { id, seed, type }; + let accounts = []; + for (let id of await db.lRange(`${user.id}:accounts`, 0, 1)) { + let account = await g(`account:${id}`); + account.balance = await g(`balance:${id}`); + + if (account.type === "savings") { + } - await bc.createWallet(id); + accounts.push(account); + } - await s(`account:${id}`, account); - await s(`balance:${id}`, 0); - await s(`pending:${id}`, 0); - await db.lPush(`${user.id}:accounts`, id); + res.send(accounts); + }, - res.send(account); + async createAccount(req, res) { + try { + let { fingerprint, pubkey, type, seed } = req.body; + let { user } = req; + + let id = v4(); + let account = { id, seed, type }; + + await bc.createWallet({ + wallet_name: id, + descriptors: true, + disable_private_keys: true, + }); + + let node = rpc({ ...config.bitcoin, wallet: id }); + + let descriptors = []; + for (let i of [0, 1]) { + let desc = `wpkh([${fingerprint}]${pubkey}/${i}/*)`; + let { checksum } = await node.getDescriptorInfo(desc); + descriptors.push({ + desc: `${desc}#${checksum}`, + range: [0, 100], + next_index: 0, + timestamp: 0, + internal: i === 1, + active: true, + }); + } + + await node.importDescriptors(descriptors); + + await s(`account:${id}`, account); + await s(`balance:${id}`, 0); + await s(`pending:${id}`, 0); + await db.lPush(`${user.id}:accounts`, id); + + res.send(account); + } catch (e) { + bail(res, e.message); + } }, }; |
