aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2024-09-12 14:14:24 -0700
committerAdam Soltys <[email protected]>2024-09-12 14:14:24 -0700
commitf76ffaf514d42f9d74698d9fddc2e98e54667578 (patch)
tree62931abc9e901384844f97358cf660f6519151c9
parentbc94288f562223ca3bd0dd969c68c6650adf6089 (diff)
downloadcoinos-server-f76ffaf514d42f9d74698d9fddc2e98e54667578.tar.gz
coinos-server-f76ffaf514d42f9d74698d9fddc2e98e54667578.zip
descriptor import
-rwxr-xr-xbun.lockbbin207855 -> 207855 bytes
-rw-r--r--index.ts1
-rw-r--r--lib/payments.ts82
-rw-r--r--package.json2
-rw-r--r--routes/users.ts83
5 files changed, 116 insertions, 52 deletions
diff --git a/bun.lockb b/bun.lockb
index 20e97e3d..792b846a 100755
--- a/bun.lockb
+++ b/bun.lockb
Binary files differ
diff --git a/index.ts b/index.ts
index 2aa96f4f..c654f4f2 100644
--- a/index.ts
+++ b/index.ts
@@ -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);
+ }
},
};