From 9bb2eb1dbaaddbd92427705bc2d4747cbf5710a6 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 28 Jul 2021 16:11:53 -0700 Subject: accept filename and pubkey for issuance --- docker-compose.yml | 1 + routes/assets.js | 231 ++++++++++++++++++++++++++++------------------------- 2 files changed, 121 insertions(+), 111 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index ad004ad7..b7987223 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,7 @@ services: container_name: app image: asoltys/coinos-server ports: + - '3118:3118' - '3119:3119' environment: NODE_ENV: development diff --git a/routes/assets.js b/routes/assets.js index 076c7057..f99301ae 100644 --- a/routes/assets.js +++ b/routes/assets.js @@ -1,5 +1,6 @@ const axios = require("axios"); const crypto = require("crypto"); +const fs = require("fs"); let fetchAssets; (fetchAssets = async () => { @@ -7,13 +8,13 @@ let fetchAssets; const { data: assets } = await axios.get( "https://assets.blockstream.info/" ); - var liquid_assets = require('./../assets.json') + var liquid_assets = require("./../assets.json"); app.set("assets", liquid_assets); } catch (e) { - var liquid_assets = require('./../assets.json') + var liquid_assets = require("./../assets.json"); if (liquid_assets) { app.set("assets", liquid_assets); - console.debug('using static assets...' + e.message); + console.debug("using static assets..." + e.message); } else { l.error("error fetching assets", e.message); res.status(500).send("error fetching assets"); @@ -27,7 +28,6 @@ app.get( "/assets", ah(async (req, res) => { if (app.get("assets")) { - const assets = app.get("assets"); const accounts = await db.Account.findAll({ @@ -61,7 +61,7 @@ app.get( res.send(assets); } else { - console.log('error getting blockstream assets'); + console.log("error getting blockstream assets"); res.status(500).send("Problem fetching blockstream asset registry data"); } }) @@ -74,20 +74,30 @@ app.post( try { const sha256 = crypto.createHash("sha256"); const { id: user_id } = req.user; - const token_address = await lq.getNewAddress("", "legacy"); - const asset_address = await lq.getNewAddress("", "legacy"); const blind = false; - const info = await lq.getAddressInfo(asset_address); - const { pubkey: issuer_pubkey } = info; const { + address, domain, name, asset_amount, + filename, token_amount, precision, + pubkey, ticker } = req.body; + + const asset_address = address + ? address + : await lq.getNewAddress("", "legacy"); + + const token_address = + token_amount && (await lq.getNewAddress("", "legacy")); + + const issuer_pubkey = pubkey + ? pubkey + : (await lq.getAddressInfo(asset_address)).pubkey; const version = 0; const contract = { @@ -98,7 +108,9 @@ app.post( ticker, version }; - + + if (filename) contract.filename = filename; + l.info("attempting issuance", req.user.username, contract); sha256.update(JSON.stringify(contract)); @@ -108,7 +120,7 @@ app.post( .reverse() .join(""); const rawtx = await lq.createRawTransaction([], { data: "00" }); - const funded = await lq.fundRawTransaction(rawtx, { feeRate: 0.000003 }); + const funded = await lq.fundRawTransaction(rawtx, { feeRate: 0.000002 }); const params = { asset_amount, asset_address, @@ -136,131 +148,128 @@ app.post( const brt = await lq.blindRawTransaction(hex, true, [], false); const srt = await lq.signRawTransactionWithWallet(brt); const allowed = (await lq.testMempoolAccept([srt.hex]))[0].allowed; - if (allowed) { - const txid = await lq.sendRawTransaction(srt.hex); + fs.writeFileSync('tx', srt.hex); + if (!allowed) throw new Error(); + const txid = await lq.sendRawTransaction(srt.hex); - await db.transaction(async transaction => { - let account = await db.Account.findOne({ - where: { - user_id, - asset: config.liquid.btcasset, - pubkey: null - }, - include: { - model: db.User, - as: "user" - }, - lock: transaction.LOCK.UPDATE, - transaction - }); - - let { user } = account; - - if (Math.round(funded.fee * SATS) > account.balance) { - l.error( - "amount exceeds balance", - asset_amount, - funded.fee, - account.balance - ); - throw new Error( - `Insufficient funds to pay fee of ${funded.fee} BTC` - ); - } + await db.transaction(async transaction => { + let account = await db.Account.findOne({ + where: { + user_id, + asset: config.liquid.btcasset, + pubkey: null + }, + include: { + model: db.User, + as: "user" + }, + transaction + }); - await account.decrement( - { balance: Math.round(funded.fee * SATS) }, - { transaction } + let { user } = account; + + if (Math.round(funded.fee * SATS) > account.balance) { + l.error( + "amount exceeds balance", + asset_amount, + funded.fee, + account.balance ); - await account.reload({ transaction }); - emit(user.username, "account", account); + throw new Error(`Insufficient funds to pay fee of ${funded.fee} BTC`); + } + + await account.decrement( + { balance: Math.round(funded.fee * SATS) }, + { transaction } + ); + await account.reload({ transaction }); + emit(user.username, "account", account); + + account = await db.Account.create( + { + asset, + contract, + domain, + user_id, + ticker, + precision, + name, + balance: 0, + network: "liquid", + pending: params.asset_amount * SATS + }, + { transaction } + ); + + emit(user.username, "account", account); + l.info( + "issued asset", + user.username, + params.asset_amount, + ticker, + name + ); + + const asset_payment = await db.Payment.create( + { + account_id: account.id, + user_id, + hash: txid, + amount: params.asset_amount * SATS, + received: true, + confirmed: false, + address: asset_address, + network: "liquid" + }, + { transaction } + ); + emit(user.username, "payment", asset_payment); + + issuances[txid] = { + user_id, + asset, + asset_amount: params.asset_amount, + asset_payment_id: asset_payment.id + }; + if (token_amount) { account = await db.Account.create( { - asset, - contract, - domain, + asset: token, user_id, - ticker, - precision, - name, + domain, + ticker: `${ticker}REISSUANCETOKEN`, + precision: 8, + name: `${name} Reissuance Token`, balance: 0, - network: "liquid", - pending: params.asset_amount * SATS + pending: token_amount * SATS }, { transaction } ); - emit(user.username, "account", account); - l.info( - "issued asset", - user.username, - params.asset_amount, - ticker, - name - ); - const asset_payment = await db.Payment.create( + const token_payment = await db.Payment.create( { account_id: account.id, user_id, hash: txid, - amount: params.asset_amount * SATS, + amount: token_amount * SATS, received: true, confirmed: false, - address: asset_address, + address: token_address, network: "liquid" }, { transaction } ); - emit(user.username, "payment", asset_payment); + emit(user.username, "payment", token_payment); - issuances[txid] = { - user_id, - asset, - asset_amount: params.asset_amount, - asset_payment_id: asset_payment.id - }; - - if (token_amount) { - account = await db.Account.create( - { - asset: token, - user_id, - domain, - ticker: `${ticker}REISSUANCETOKEN`, - precision: 8, - name: `${name} Reissuance Token`, - balance: 0, - pending: token_amount * SATS - }, - { transaction } - ); - emit(user.username, "account", account); - - const token_payment = await db.Payment.create( - { - account_id: account.id, - user_id, - hash: txid, - amount: token_amount * SATS, - received: true, - confirmed: false, - address: token_address, - network: "liquid" - }, - { transaction } - ); - emit(user.username, "payment", token_payment); - - issuances[txid].token = token; - issuances[txid].token_amount = token_amount; - issuances[txid].token_payment_id = token_payment.id; - } - }); + issuances[txid].token = token; + issuances[txid].token_amount = token_amount; + issuances[txid].token_payment_id = token_payment.id; + } + }); - res.send(issuances[txid]); - } + res.send(issuances[txid]); } catch (e) { l.error("asset issuance failed", e.message); res.status(500).send(e.message); -- cgit v1.2.3 From 51b001661bc8f92161569c0817df6bc73f66edd1 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 29 Jul 2021 11:55:18 -0700 Subject: change overrides so default is to use a dev build rather than dev webpack --- .gitignore | 1 - docker-compose.dev.yml | 13 +++++++++++++ docker-compose.override.yml | 11 +++++++++++ docker-compose.yml | 9 --------- sample.override.yml | 13 ------------- 5 files changed, 24 insertions(+), 23 deletions(-) create mode 100644 docker-compose.dev.yml create mode 100644 docker-compose.override.yml delete mode 100644 sample.override.yml diff --git a/.gitignore b/.gitignore index 2537982f..58f3c4fe 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,3 @@ lnurl-server.sqlite3 watcher.log config mysql -docker-compose.override.yml diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 00000000..00397499 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,13 @@ +version: '3.6' +services: + ui: + depends_on: + - app + container_name: ui + image: asoltys/coinos-ui:dev + ports: + - '8085:8085' + environment: + NODE_ENV: development + volumes: + - ../coinos-ui:/coinos diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 00000000..b7318355 --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,11 @@ +version: '3.6' +services: + ui: + container_name: ui + image: asoltys/coinos-ui + ports: + - 80:80 + volumes: + - ./config/nginx:/etc/nginx/templates + environment: + HOST: ${HOST} diff --git a/docker-compose.yml b/docker-compose.yml index b7987223..febdba4f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,14 +1,5 @@ version: '3.6' services: - ui: - container_name: ui - image: asoltys/coinos-ui - ports: - - 80:80 - volumes: - - ./config/nginx:/etc/nginx/templates - environment: - HOST: ${HOST} app: container_name: app image: asoltys/coinos-server diff --git a/sample.override.yml b/sample.override.yml deleted file mode 100644 index 00397499..00000000 --- a/sample.override.yml +++ /dev/null @@ -1,13 +0,0 @@ -version: '3.6' -services: - ui: - depends_on: - - app - container_name: ui - image: asoltys/coinos-ui:dev - ports: - - '8085:8085' - environment: - NODE_ENV: development - volumes: - - ../coinos-ui:/coinos -- cgit v1.2.3 From 2bb07ecf3be34963a93808144075bb58a49fde4d Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 30 Jul 2021 11:14:28 -0700 Subject: bash script to mine blocks in regtest mode --- mine.sh | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100755 mine.sh diff --git a/mine.sh b/mine.sh new file mode 100755 index 00000000..a0891b51 --- /dev/null +++ b/mine.sh @@ -0,0 +1,8 @@ +#!/bin/bash + + +while true +do + docker exec -it liquid elements-cli -rpcwallet=a -datadir=/config generatetoaddress 1 $(docker exec -it liquid elements-cli -rpcwallet=a -datadir=/config getnewaddress) + sleep 8 +done -- cgit v1.2.3 From e44bd5d724f2408b0b8c249c45c254f4dce68754 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sun, 1 Aug 2021 20:47:34 -0700 Subject: typo --- sampleconfig/liquid/elements.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sampleconfig/liquid/elements.conf b/sampleconfig/liquid/elements.conf index bfd01638..ce93b307 100644 --- a/sampleconfig/liquid/elements.conf +++ b/sampleconfig/liquid/elements.conf @@ -7,7 +7,7 @@ rpcuser=admin1 rpcpassword=123 rpcbind=0.0.0.0 rpcallowip=0.0.0.0/0 -rcpwallet=a +rpcwallet=a validatepegin=0 initialfreecoins=2100000000000000 -- cgit v1.2.3 From a7168a10f45c305a45d2e56db6fbfeef210f1b9b Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 2 Aug 2021 02:05:28 -0700 Subject: silhouettes --- routes/assets.js | 7 ++--- routes/liquid/taxi.js | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++ routes/payments.js | 1 + 3 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 routes/liquid/taxi.js diff --git a/routes/assets.js b/routes/assets.js index f99301ae..2494ee94 100644 --- a/routes/assets.js +++ b/routes/assets.js @@ -111,7 +111,7 @@ app.post( if (filename) contract.filename = filename; - l.info("attempting issuance", req.user.username, contract); + l.info("attempting issuance", req.user.username, contract, "address", address, "pubkey", pubkey); sha256.update(JSON.stringify(contract)); const hash = sha256.digest("hex"); @@ -149,7 +149,7 @@ app.post( const srt = await lq.signRawTransactionWithWallet(brt); const allowed = (await lq.testMempoolAccept([srt.hex]))[0].allowed; fs.writeFileSync('tx', srt.hex); - if (!allowed) throw new Error(); + if (!allowed) throw new Error("issuance rejected by mempool"); const txid = await lq.sendRawTransaction(srt.hex); await db.transaction(async transaction => { @@ -185,6 +185,7 @@ app.post( await account.reload({ transaction }); emit(user.username, "account", account); + if (address) return; account = await db.Account.create( { asset, @@ -271,7 +272,7 @@ app.post( res.send(issuances[txid]); } catch (e) { - l.error("asset issuance failed", e.message); + l.error("asset issuance failed", e.message, e.stack); res.status(500).send(e.message); } }) diff --git a/routes/liquid/taxi.js b/routes/liquid/taxi.js new file mode 100644 index 00000000..3f450752 --- /dev/null +++ b/routes/liquid/taxi.js @@ -0,0 +1,74 @@ +const { ECPair, Psbt, payments, networks } = require("@asoltys/liquidjs-lib"); +const wretch = require("wretch"); +const fetch = require("node-fetch"); +wretch().polyfills({ fetch }); +const url = prod + ? "https://blockstream.info/liquid/api" + : "http://localhost:3002"; +const electrs = wretch().url(url); +const network = networks.regtest; + +module.exports = ah(async (req, res) => { + let { user } = req; + let { psbt } = req.body; + + let utxos = await electrs + .url(`/address/XLDLqkbbNKvgS9vhj3fQWPv1Y3UNoHEneZ/utxo`) + .get() + .json(); + let tx = utxos.find(tx => tx.value >= 150); + let hex = await electrs + .url(`/tx/${tx.txid}/hex/`) + .get() + .text(); + + let redeemScript = payments.p2wpkh({ + pubkey: Buffer.from( + "03c42e2b536630da346d8f225797406eb86700239d627101a61e26e6d544db072e", + "hex" + ), + network + }).output; + + let p = Psbt.fromBase64(psbt) + .addInput({ + hash: tx.txid, + index: tx.vout, + redeemScript, + nonWitnessUtxo: Buffer.from(hex, "hex") + }) + .addOutput({ + asset: network.assetHash, + nonce: Buffer.alloc(1, 0), + script: Buffer.alloc(0), + value: 150 + }) + .addOutput({ + asset, + nonce: Buffer.alloc(1), + script: Address.toOutputScript( + "XLDLqkbbNKvgS9vhj3fQWPv1Y3UNoHEneZ", + network + ), + value: tx.value - 150 + }) + .signInput( + 1, + ECPair.fromPrivateKey( + Buffer.from( + "6802b8dfed3733e3b147582810c431150444c026066796b808c36f7089d02773", + "hex" + ) + ) + ); + + try { + if ((user.username = "silhouettes")) { + res.send({ psbt: p.toBase64() }); + } else { + throw new Error("taxi service only available to select customers"); + } + } catch (e) { + return res.status(500).send(e); + } +}); diff --git a/routes/payments.js b/routes/payments.js index a4bae562..a749e333 100644 --- a/routes/payments.js +++ b/routes/payments.js @@ -142,6 +142,7 @@ ah(async () => { app.get("/liquid/generate", auth, require("./liquid/generate")); app.post("/liquid/fee", auth, require("./liquid/fee")); app.post("/liquid/send", auth, require("./liquid/send")); + app.post("/taxi", auth, require("./liquid/taxi")); require("./liquid/receive"); setTimeout(async () => { -- cgit v1.2.3 From ea5b089a4bc7e72d184dbd24c9043b8101cf9f08 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 2 Aug 2021 02:06:21 -0700 Subject: drop wallet from mine script --- mine.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mine.sh b/mine.sh index a0891b51..0df5b6ba 100755 --- a/mine.sh +++ b/mine.sh @@ -3,6 +3,6 @@ while true do - docker exec -it liquid elements-cli -rpcwallet=a -datadir=/config generatetoaddress 1 $(docker exec -it liquid elements-cli -rpcwallet=a -datadir=/config getnewaddress) + docker exec -it liquid elements-cli -datadir=/config generatetoaddress 1 $(docker exec -it liquid elements-cli -datadir=/config getnewaddress) sleep 8 done -- cgit v1.2.3 From 48e0438048b1c8bca8dcc5a895282ac7ddd4dd84 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Tue, 3 Aug 2021 00:18:16 -0700 Subject: taxi endpoint --- routes/liquid/taxi.js | 121 +++++++++++++++++++++++++++----------------------- 1 file changed, 66 insertions(+), 55 deletions(-) diff --git a/routes/liquid/taxi.js b/routes/liquid/taxi.js index 3f450752..14da9411 100644 --- a/routes/liquid/taxi.js +++ b/routes/liquid/taxi.js @@ -1,74 +1,85 @@ -const { ECPair, Psbt, payments, networks } = require("@asoltys/liquidjs-lib"); +const { + address: Address, + ECPair, + Psbt, + payments, + networks +} = require("@asoltys/liquidjs-lib"); const wretch = require("wretch"); const fetch = require("node-fetch"); wretch().polyfills({ fetch }); const url = prod ? "https://blockstream.info/liquid/api" - : "http://localhost:3002"; + : "http://electrs-liquid:3002"; const electrs = wretch().url(url); const network = networks.regtest; +const key = ECPair.fromPrivateKey(Buffer.from(config.taxi, "hex")); + module.exports = ah(async (req, res) => { let { user } = req; let { psbt } = req.body; - let utxos = await electrs - .url(`/address/XLDLqkbbNKvgS9vhj3fQWPv1Y3UNoHEneZ/utxo`) - .get() - .json(); - let tx = utxos.find(tx => tx.value >= 150); - let hex = await electrs - .url(`/tx/${tx.txid}/hex/`) - .get() - .text(); - - let redeemScript = payments.p2wpkh({ - pubkey: Buffer.from( - "03c42e2b536630da346d8f225797406eb86700239d627101a61e26e6d544db072e", - "hex" - ), - network - }).output; - - let p = Psbt.fromBase64(psbt) - .addInput({ - hash: tx.txid, - index: tx.vout, - redeemScript, - nonWitnessUtxo: Buffer.from(hex, "hex") - }) - .addOutput({ - asset: network.assetHash, - nonce: Buffer.alloc(1, 0), - script: Buffer.alloc(0), - value: 150 - }) - .addOutput({ - asset, - nonce: Buffer.alloc(1), - script: Address.toOutputScript( - "XLDLqkbbNKvgS9vhj3fQWPv1Y3UNoHEneZ", - network - ), - value: tx.value - 150 - }) - .signInput( - 1, - ECPair.fromPrivateKey( - Buffer.from( - "6802b8dfed3733e3b147582810c431150444c026066796b808c36f7089d02773", - "hex" - ) - ) - ); - try { - if ((user.username = "silhouettes")) { - res.send({ psbt: p.toBase64() }); - } else { + if (user.username !== "silhouettes") { throw new Error("taxi service only available to select customers"); } + + let redeem = payments.p2wpkh({ + pubkey: key.publicKey, + network + }); + + let out = payments.p2sh({ + redeem, + network + }); + + let utxos = await electrs + .url(`/address/${out.address}/utxo`) + .get() + .json(); + + let tx = utxos.find(tx => tx.value >= 150); + + let hex = await electrs + .url(`/tx/${tx.txid}/hex`) + .get() + .text(); + + let raw = Psbt.fromBase64(psbt) + .addInput({ + hash: tx.txid, + index: tx.vout, + redeemScript: redeem.output, + nonWitnessUtxo: Buffer.from(hex, "hex") + }) + .addOutput({ + asset: network.assetHash, + nonce: Buffer.alloc(1, 0), + script: Buffer.alloc(0), + value: 150 + }) + .addOutput({ + asset: network.assetHash, + nonce: Buffer.alloc(1), + script: out.output, + value: tx.value - 150 + }) + .signInput(1, key) + .finalizeInput(1) + .extractTransaction() + .toHex(); + + let txid = await electrs + .url("/tx") + .body(raw) + .post() + .text(); + + res.send({ txid }); } catch (e) { + console.log(e); return res.status(500).send(e); } }); -- cgit v1.2.3 From 6abfe155c5bb3845f59775b95eeb9d4034aa92b8 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Tue, 3 Aug 2021 00:19:50 -0700 Subject: return asset even for remote issuances --- routes/assets.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/assets.js b/routes/assets.js index 2494ee94..3f483a28 100644 --- a/routes/assets.js +++ b/routes/assets.js @@ -270,7 +270,7 @@ app.post( } }); - res.send(issuances[txid]); + res.send(issuances[txid] ? issuances[txid] : { asset }); } catch (e) { l.error("asset issuance failed", e.message, e.stack); res.status(500).send(e.message); -- cgit v1.2.3