diff options
| author | Adam Soltys <[email protected]> | 2025-11-26 22:36:59 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-02-06 08:58:10 -0800 |
| commit | bc953b56f3908e3a557976b9029b617dad751755 (patch) | |
| tree | 6e65399fd615c6cf5c7e9382b212a707531e4886 | |
| parent | e95e3b8e35a858cc200de6c0b74e3e172e4f6d6e (diff) | |
| download | coinos-server-bc953b56f3908e3a557976b9029b617dad751755.tar.gz coinos-server-bc953b56f3908e3a557976b9029b617dad751755.zip | |
ark
| -rwxr-xr-x | bun.lockb | bin | 230690 -> 231433 bytes | |||
| -rw-r--r-- | lib/invoices.ts | 19 | ||||
| -rw-r--r-- | lib/utils.ts | 4 | ||||
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | routes/invoices.ts | 6 | ||||
| -rw-r--r-- | routes/users.ts | 88 |
6 files changed, 104 insertions, 14 deletions
| Binary files differ diff --git a/lib/invoices.ts b/lib/invoices.ts index 9666d217..6ddffe54 100644 --- a/lib/invoices.ts +++ b/lib/invoices.ts @@ -63,17 +63,16 @@ export const generate = async ({ invoice, user }) => { let text; let paymentHash; - if (account.seed) { - if (type !== PaymentType.ark) { - type = PaymentType.bitcoin; - const node = rpc({ ...config[type], wallet: aid }); - hash = await node.getNewAddress({ address_type }); - text = bip21(hash, invoice); + if (account.type === "ark") { + type = PaymentType.ark; + text = account.arkAddress; + } else if (account.seed) { + type = PaymentType.bitcoin; + const node = rpc({ ...config[type], wallet: aid }); + hash = await node.getNewAddress({ address_type }); + text = bip21(hash, invoice); - ({ hdkeypath: path } = await node.getAddressInfo(hash)); - } else { - text = hash; - } + ({ hdkeypath: path } = await node.getAddressInfo(hash)); } else if (type === PaymentType.lightning) { let r; if (bolt11) { diff --git a/lib/utils.ts b/lib/utils.ts index add09782..878ef037 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -47,6 +47,10 @@ export const bip21 = (address, { amount, memo, tip, type }) => { return `${network}:${address}?${url.toString()}`; }; +export const getAccount = async (id) => { + return g(`account:${id}`); +} + export const getUser = async (username, fields = undefined) => { if (username === "undefined") fail("invalid user"); const k = username?.replace(/\s/g, "").toLowerCase(); diff --git a/package.json b/package.json index 71ce64f7..f682fcf7 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "buffer-crc32": "^0.2.13", "clightning-client": "github:asoltys/lightning-client-js#master", "date-fns": "^2.30.0", + "eventsource": "^4.1.0", "fastify": "^4.29.1", "file-type": "^18.7.0", "fluent-ffmpeg": "^2.1.3", diff --git a/routes/invoices.ts b/routes/invoices.ts index 63338621..bc96c3f0 100644 --- a/routes/invoices.ts +++ b/routes/invoices.ts @@ -2,9 +2,8 @@ import config from "$config"; import { db, g, gf, s } from "$lib/db"; import { generate } from "$lib/invoices"; import { err } from "$lib/logging"; -import { bail, fail, fields, getInvoice, getUser } from "$lib/utils"; +import { bail, fail, fields, getAccount, getInvoice, getUser } from "$lib/utils"; import rpc from "@coinos/rpc"; -import { v4 } from "uuid"; export default { async get(req, res) { @@ -18,6 +17,7 @@ export default { if (invoice) { invoice.secret = undefined; invoice.user = await getUser(invoice.uid, fields); + invoice.account = await getAccount(invoice.aid); invoice.items ||= []; } @@ -40,6 +40,8 @@ export default { const result = await generate({ invoice, user }); res.send(result); } catch (e) { + console.trace(); + console.log(e); err( "problem generating invoice", req.user?.username, diff --git a/routes/users.ts b/routes/users.ts index a25b6a8e..f564d09f 100644 --- a/routes/users.ts +++ b/routes/users.ts @@ -11,6 +11,8 @@ import { emit } from "$lib/sockets"; import upload from "$lib/upload"; import { bail, fail, fields, getUser, pick } from "$lib/utils"; import whitelist from "$lib/whitelist"; +import rpc from "@coinos/rpc"; +import { bech32m } from "@scure/base"; import { $ } from "bun"; import got from "got"; @@ -835,6 +837,21 @@ export default { account.balance = await g(`balance:${id}`); accounts.push(account); } + + if (account.type === PaymentType.ark) { + const decoded = bech32m.decode(account.arkAddress, 1023); + const data = new Uint8Array(bech32m.fromWords(decoded.words)); + + const vtxoTaprootKey = data.slice(33, 65); + + const vtxoHex = Buffer.from(vtxoTaprootKey).toString("hex"); + const r = await fetch( + `http://arkd:7070/v1/indexer/vtxos?scripts=5120${vtxoHex}`, + { "content-type": "application/json" }, + ); + const { vtxos } = await r.json(); + account.balance = vtxos.reduce((a, b) => a + b.amount, 0); + } } res.send(accounts.reverse()); @@ -846,11 +863,68 @@ export default { async createAccount(req, res) { try { - const { name, type } = req.body; + const { fingerprint, pubkey, name, seed, type, arkAddress, accountIndex } = + req.body; const { user } = req; + const { id: uid } = user; const id = v4(); - const account = { id, name, type, uid: user.id }; + const account = { + id, + name, + seed, + type, + uid, + descriptors: [], + arkAddress, + accountIndex, + }; + + // ARK accounts don't need Bitcoin Core wallet setup + if (type === "ark") { + await db + .multi() + .set(`account:${id}`, JSON.stringify(account)) + .set(`balance:${id}`, 0) + .set(`pending:${id}`, 0) + .lPush(`${user.id}:accounts`, id) + .exec(); + + res.send(account); + return; + } + + // Bitcoin on-chain wallet setup + let node = rpc(config[type]); + + await node.createWallet({ + wallet_name: id, + descriptors: true, + disable_private_keys: true, + load_on_startup: true, + }); + + node = rpc({ ...config[type], wallet: id }); + + for (const i of [0, 1]) { + const desc = `wpkh([${fingerprint}]${pubkey}/${i}/*)`; + const { checksum } = await node.getDescriptorInfo(desc); + account.descriptors.push({ + desc: `${desc}#${checksum}`, + range: [0, 100], + next_index: 0, + timestamp: "now", + internal: i === 1, + active: true, + }); + } + + await node.importDescriptors(account.descriptors); + + if (typeof accountIndex !== "undefined") { + user.accountIndex = (Number.parseInt(accountIndex) || 0) + 1; + await s(`user:${uid}`, user); + } await db .multi() @@ -890,6 +964,16 @@ export default { const pos = await db.lPos(`${uid}:accounts`, id); if (!(account && pos != null)) fail("account not found"); + // Only unload Bitcoin Core wallet for non-ARK accounts + if (account.type !== "ark") { + try { + const node = rpc({ ...config[account.type], wallet: id }); + await node.unloadWallet(id); + } catch (e) { + warn("failed to unload wallet", id); + } + } + await db .multi() .lRem(`${uid}:accounts`, 1, id) |
