aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2020-09-18 22:54:12 -0700
committerAdam Soltys <[email protected]>2020-09-18 22:54:12 -0700
commitc615a409acfdfb6bf1f4bc11651845979dea2e79 (patch)
tree47f988d11cc418fa1a9e20f73b758c8d1efae11a
parentae48f3881cd6bf300c629d3332b2b72561e8e632 (diff)
downloadcoinos-server-c615a409acfdfb6bf1f4bc11651845979dea2e79.tar.gz
coinos-server-c615a409acfdfb6bf1f4bc11651845979dea2e79.zip
fee faucets
-rw-r--r--routes/assets.js515
-rw-r--r--routes/liquid/send.js118
2 files changed, 348 insertions, 285 deletions
diff --git a/routes/assets.js b/routes/assets.js
index c476b3f5..34856f04 100644
--- a/routes/assets.js
+++ b/routes/assets.js
@@ -8,7 +8,7 @@ let fetchAssets;
"https://assets.blockstream.info/"
);
- app.set('assets', assets);
+ app.set("assets", assets);
} catch (e) {
l.error("error fetching assets", e.message);
res.status(500).send("error fetching assets");
@@ -17,287 +17,279 @@ let fetchAssets;
setTimeout(fetchAssets, 7200000);
})();
-app.get("/assets", ah(async (req, res) => {
- if (app.get('assets')) {
- const assets = app.get('assets');
- const accounts = await db.Account.findAll({
- group: ["asset"],
- });
-
- Object.keys(assets).map((a) => (assets[a].registered = true));
-
- accounts.map((a) => {
- if (!assets[a.asset]) assets[a.asset] = a;
- });
-
- res.send(assets);
- }
- else app.status(500).send('Problem fetching blockstream asset registry data');
-}));
+app.get(
+ "/assets",
+ ah(async (req, res) => {
+ if (app.get("assets")) {
+ const assets = app.get("assets");
+ const accounts = await db.Account.findAll({
+ group: ["asset"]
+ });
-app.post("/assets", auth, ah(async (req, res) => {
- 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;
+ Object.keys(assets).map(a => (assets[a].registered = true));
- const {
- domain,
- name,
- asset_amount,
- token_amount,
- precision,
- ticker,
- } = req.body;
- const version = 0;
+ accounts.map(a => {
+ if (!assets[a.asset]) assets[a.asset] = a;
+ });
- const contract = {
- entity: { domain },
- issuer_pubkey,
- name,
- precision,
- ticker,
- version,
- };
+ res.send(assets);
+ } else
+ app.status(500).send("Problem fetching blockstream asset registry data");
+ })
+);
- sha256.update(JSON.stringify(contract));
- const hash = sha256.digest("hex");
- const contract_hash = hash
- .match(/[a-f0-9]{2}/g)
- .reverse()
- .join("");
- const rawtx = await lq.createRawTransaction([], { data: "00" });
- const funded = await lq.fundRawTransaction(rawtx, { feeRate: 0.000003 });
- const params = {
- asset_amount,
- asset_address,
- blind,
- contract_hash,
- };
+app.post(
+ "/assets",
+ auth,
+ ah(async (req, res) => {
+ 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;
- params.asset_amount = asset_amount / (SATS / 10 ** precision);
+ const {
+ domain,
+ name,
+ asset_amount,
+ token_amount,
+ precision,
+ ticker
+ } = req.body;
+ const version = 0;
- l.info("asset_amount", params.asset_amount);
- if (token_amount) {
- params.token_amount = token_amount;
- params.token_address = token_address;
- params.token_amount = parseInt(params.token_amount);
- }
+ const contract = {
+ entity: { domain },
+ issuer_pubkey,
+ name,
+ precision,
+ ticker,
+ version
+ };
- let ria;
- try {
- ria = await lq.rawIssueAsset(funded.hex, [params]);
- } catch (e) {
- l.info(asset_amount, token_amount, params);
- throw new Error(e.message);
- }
+ sha256.update(JSON.stringify(contract));
+ const hash = sha256.digest("hex");
+ const contract_hash = hash
+ .match(/[a-f0-9]{2}/g)
+ .reverse()
+ .join("");
+ const rawtx = await lq.createRawTransaction([], { data: "00" });
+ const funded = await lq.fundRawTransaction(rawtx, { feeRate: 0.000003 });
+ const params = {
+ asset_amount,
+ asset_address,
+ blind,
+ contract_hash
+ };
- const { asset, hex, token } = ria[0];
- 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);
+ params.asset_amount = asset_amount / (SATS / 10 ** precision);
- 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,
- });
+ l.info("asset_amount", params.asset_amount);
+ if (token_amount) {
+ params.token_amount = token_amount;
+ params.token_address = token_address;
+ params.token_amount = parseInt(params.token_amount);
+ }
- let { user } = account;
+ let ria;
+ try {
+ ria = await lq.rawIssueAsset(funded.hex, [params]);
+ } catch (e) {
+ l.info(asset_amount, token_amount, params);
+ throw new Error(e.message);
+ }
- 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`);
- }
+ const { asset, hex, token } = ria[0];
+ 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);
- account.balance -= Math.round(funded.fee * SATS);
- await account.save({ 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 }
- );
+ 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
+ });
- emit(user.username, "account", account);
+ let { user } = account;
- 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);
+ 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`
+ );
+ }
- issuances[txid] = {
- user_id,
- asset,
- asset_amount: params.asset_amount,
- asset_payment_id: asset_payment.id,
- };
+ account.balance -= Math.round(funded.fee * SATS);
+ await account.save({ transaction });
+ emit(user.username, "account", account);
- if (token_amount) {
account = await db.Account.create(
{
- asset: token,
- user_id,
+ asset,
+ contract,
domain,
- ticker: `${ticker}REISSUANCETOKEN`,
- precision: 8,
- name: `${name} Reissuance Token`,
+ user_id,
+ ticker,
+ precision,
+ name,
balance: 0,
- pending: token_amount * SATS,
+ network: "liquid",
+ pending: params.asset_amount * SATS
},
{ transaction }
);
+
emit(user.username, "account", account);
- const token_payment = await db.Payment.create(
+ const asset_payment = await db.Payment.create(
{
account_id: account.id,
user_id,
hash: txid,
- amount: token_amount * SATS,
+ amount: params.asset_amount * SATS,
received: true,
confirmed: false,
- address: token_address,
- network: "liquid",
+ address: asset_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;
- }
- });
-
- res.send(issuances[txid]);
- }
- } catch (e) {
- l.error("asset issuance failed", e.message);
- res.status(500).send(e.message);
- }
-}));
+ emit(user.username, "payment", asset_payment);
-app.post("/assets/register", auth, ah(async (req, res) => {
- const { asset } = req.body;
- const account = await db.Account.findOne({
- where: {
- user_id: req.user.id,
- asset,
- },
- });
+ issuances[txid] = {
+ user_id,
+ asset,
+ asset_amount: params.asset_amount,
+ asset_payment_id: asset_payment.id
+ };
- try {
- const { data: result } = await axios.post(
- "https://assets.blockstream.info/",
- {
- asset_id: asset,
- contract: account.contract,
- }
- );
- l.info("register asset result", req.user.username, result);
- res.send(result);
- } catch (e) {
- l.error("asset registration failed", e.message);
- res.status(500).send(e.message);
- }
-}));
+ 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);
-app.post("/loadFaucet", auth, ah(async (req, res) => {
- const { asset, amount } = req.body;
+ 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);
- try {
- await db.transaction(async transaction => {
- let { account } = user;
+ issuances[txid].token = token;
+ issuances[txid].token_amount = token_amount;
+ issuances[txid].token_payment_id = token_payment.id;
+ }
+ });
- if (account.balance < amount) {
- throw new Error("Insufficient funds");
+ res.send(issuances[txid]);
}
+ } catch (e) {
+ l.error("asset issuance failed", e.message);
+ res.status(500).send(e.message);
+ }
+ })
+);
- let fee = 0;
+app.post(
+ "/assets/register",
+ auth,
+ ah(async (req, res) => {
+ const { asset } = req.body;
+ const account = await db.Account.findOne({
+ where: {
+ user_id: req.user.id,
+ asset
+ }
+ });
- account.balance -= amount;
- await account.save({ transaction });
+ try {
+ const { data: result } = await axios.post(
+ "https://assets.blockstream.info/",
+ {
+ asset_id: asset,
+ contract: account.contract
+ }
+ );
+ l.info("register asset result", req.user.username, result);
+ res.send(result);
+ } catch (e) {
+ l.error("asset registration failed", e.message);
+ res.status(500).send(e.message);
+ }
+ })
+);
- let params = {
- amount: -amount,
- account_id: account.id,
- memo,
- user_id: user.id,
- rate: app.get("rates")[user.currency],
- currency: user.currency,
- confirmed: true,
- hash: "Internal Transfer",
- network: "COINOS"
- };
+app.post(
+ "/loadFaucet",
+ auth,
+ ah(async (req, res) => {
+ const { user } = req;
+ const { asset, amount } = req.body;
- let payment = await db.Payment.create(params, { transaction });
+ try {
+ await db.transaction(async transaction => {
+ let { account } = user;
- payment = payment.get({ plain: true });
- payment.account = account.get({ plain: true });
+ if (account.asset !== config.liquid.btcasset)
+ throw new Error(
+ "Faucet has to be funded with bitcoin. Try sending from another wallet."
+ );
+ if (account.balance < amount) throw new Error("Insufficient funds");
- l.info("sent internal", user.username, -payment.amount);
+ let fee = 0;
- emit(user.username, "payment", payment);
- emit(user.username, "account", account);
- emit(user.username, "user", user);
+ account.balance -= amount;
+ await account.save({ transaction });
let a2;
let acc = {
user_id: null,
- asset,
+ asset
};
- if (recipient.account.asset === asset && !recipient.account.pubkey)
- ({ account: a2 } = recipient);
- else {
- a2 = await db.Account.findOne({
- where: acc,
- lock: transaction.LOCK.UPDATE,
- transaction
- });
- }
+ a2 = await db.Account.findOne({
+ where: acc,
+ lock: transaction.LOCK.UPDATE,
+ transaction
+ });
if (a2) {
a2.balance += amount;
@@ -308,7 +300,7 @@ app.post("/loadFaucet", auth, ah(async (req, res) => {
let ticker = asset.substr(0, 3).toUpperCase();
let precision = 8;
- const assets = app.get('assets');
+ const assets = app.get("assets");
if (assets[asset]) {
({ domain, ticker, precision, name } = assets[asset]);
@@ -329,20 +321,61 @@ app.post("/loadFaucet", auth, ah(async (req, res) => {
acc = { ...acc, ...{ domain, ticker, precision, name } };
acc.balance = amount;
acc.pending = 0;
- acc.network = 'liquid';
+ acc.network = "liquid";
a2 = await db.Account.create(acc, { transaction });
}
+ let params = {
+ amount: -amount,
+ account_id: account.id,
+ user_id: user.id,
+ rate: app.get("rates")[user.currency],
+ currency: user.currency,
+ confirmed: true,
+ hash: `Loaded Faucet - ${a2.ticker}`,
+ network: "COINOS"
+ };
+
+ let payment = await db.Payment.create(params, { transaction });
+
+ payment = payment.get({ plain: true });
+ payment.account = account.get({ plain: true });
+
+ l.info("sent internal", user.username, -payment.amount);
+
+ emit(user.username, "payment", payment);
+ emit(user.username, "account", account);
+ emit(user.username, "user", user);
+
l.info("loaded faucet", asset, amount);
res.end();
+ });
+ } catch (e) {
+ l.error("problem loading faucet", user.username, user.balance, e.message, e.stack);
+ return res.status(500).send(e.message);
+ throw(e);
+ }
+ })
+);
+
+app.get(
+ "/faucet",
+ auth,
+ ah(async (req, res) => {
+ let { asset } = req.query;
+ let faucet = await db.Account.findOne({
+ where: {
+ asset,
+ user_id: null,
+ }
});
- } catch (e) {
- l.error(
- "problem loading faucet",
- user.username,
- user.balance,
- e.message,
- );
- return res.status(500).send(e.message);
- }
-}));
+
+ if (!faucet) faucet = {
+ asset,
+ balance: 0,
+ ticker: 'Unknown',
+ };
+
+ res.send(faucet);
+ })
+);
diff --git a/routes/liquid/send.js b/routes/liquid/send.js
index a38f84e2..0562c750 100644
--- a/routes/liquid/send.js
+++ b/routes/liquid/send.js
@@ -1,8 +1,10 @@
+const btc = config.liquid.btcasset;
+
module.exports = ah(async (req, res) => {
let { user } = req;
let { address, memo, tx } = req.body;
- const isChange = async (address) =>
+ const isChange = async address =>
(await lq.getAddressInfo(address)).ismine &&
!Object.keys(addresses).includes(address);
@@ -16,7 +18,7 @@ module.exports = ah(async (req, res) => {
let {
asset,
value,
- scriptPubKey: { type, addresses },
+ scriptPubKey: { type, addresses }
} = vout[i];
if (type === "fee") fee = toSats(value);
@@ -36,53 +38,79 @@ module.exports = ah(async (req, res) => {
const payments = [];
try {
- await db.transaction(async (transaction) => {
+ await db.transaction(async transaction => {
for (let i = 0; i < assets.length; i++) {
let asset = assets[i];
let amount = totals[asset];
if (change[asset]) amount -= change[asset];
let total = amount;
- if (asset === config.liquid.btcasset) total += fee;
- l.info("creating liquid payment", user.username, asset, total, fee);
+ if (asset === btc) {
+ let covered = 0;
+ let nonbtc = assets.filter(a => a !== btc);
+ if (nonbtc.length === 1) {
+ let faucet = await db.Account.findOne({
+ where: {
+ asset: nonbtc[0],
+ user_id: null
+ }
+ });
- let account = await db.Account.findOne({
- where: {
- user_id: user.id,
- asset,
- },
- lock: transaction.LOCK.UPDATE,
- transaction,
- });
+ if (faucet) {
+ covered = faucet.balance;
+ if (covered > fee) covered = fee;
+ faucet.balance -= covered;
+ await faucet.save({ transaction });
+ }
- if (total > account.balance) {
- l.warn("amount exceeds balance", {
- total,
- fee,
- balance: account.balance,
- });
- throw new Error("Insufficient funds");
+ total += fee - covered;
+ }
}
- account.balance -= total;
- await account.save({ transaction });
+ if (asset !== btc || total) {
+ l.info("creating liquid payment", user.username, asset, total, fee);
+
+ let account = await db.Account.findOne({
+ where: {
+ user_id: user.id,
+ asset,
+ pubkey: null
+ },
+ lock: transaction.LOCK.UPDATE,
+ transaction
+ });
- let payment = {
- amount: -amount,
- account_id: account.id,
- fee,
- memo,
- user_id: user.id,
- rate: app.get("rates")[user.currency],
- currency: user.currency,
- address,
- confirmed: true,
- received: false,
- network: "liquid",
- };
+ if (total > account.balance) {
+ l.warn("amount exceeds balance", {
+ total,
+ fee,
+ balance: account.balance
+ });
+ throw new Error(
+ `Insufficient funds, need ${total} ${account.ticker}, have ${account.balance}`
+ );
+ }
- payment.account = account;
- payments.push(payment);
+ account.balance -= total;
+ await account.save({ transaction });
+
+ let payment = {
+ amount: -amount,
+ account_id: account.id,
+ fee,
+ memo,
+ user_id: user.id,
+ rate: app.get("rates")[user.currency],
+ currency: user.currency,
+ address,
+ confirmed: true,
+ received: false,
+ network: "liquid"
+ };
+
+ payment.account = account;
+ payments.push(payment);
+ }
}
});
} catch (e) {
@@ -91,7 +119,7 @@ module.exports = ah(async (req, res) => {
}
try {
- await db.transaction(async (transaction) => {
+ await db.transaction(async transaction => {
if (config.liquid.walletpass)
await lq.walletPassphrase(config.liquid.walletpass, 300);
@@ -102,12 +130,14 @@ module.exports = ah(async (req, res) => {
let main;
for (let i = 0; i < assets.length; i++) {
p = payments[i];
- let { account } = p;
- p.hash = txid;
- p = await db.Payment.create(p, { transaction });
- if (account.ticker !== "BTC" || !main) {
- main = p.get({ plain: true });
- main.account = account.get({ plain: true });
+ if (p) {
+ let { account } = p;
+ p.hash = txid;
+ p = await db.Payment.create(p, { transaction });
+ if (account.ticker !== "BTC" || !main) {
+ main = p.get({ plain: true });
+ main.account = account.get({ plain: true });
+ }
}
}