aboutsummaryrefslogtreecommitdiffstats
path: root/src/routes
diff options
context:
space:
mode:
authorAdam Soltys <asoltys@gmail.com>2022-07-25 21:45:41 -0700
committerAdam Soltys <asoltys@gmail.com>2022-07-25 21:45:41 -0700
commit4aa1ad89a0e2d489bc6b85ab3291719e080757ad (patch)
treeff2d9463f454bc6347be0f782ab1c5a29a1a23cb /src/routes
parenteb9a9c048ebcda09df0ba31745b9d9a5b543596f (diff)
downloadcoinos-ui-4aa1ad89a0e2d489bc6b85ab3291719e080757ad.tar.gz
coinos-ui-4aa1ad89a0e2d489bc6b85ab3291719e080757ad.zip
socket reconnection logic
Diffstat (limited to 'src/routes')
-rw-r--r--src/routes/__layout.svelte19
-rw-r--r--src/routes/invoice.js4
-rw-r--r--src/routes/invoice.svelte15
3 files changed, 11 insertions, 27 deletions
diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte
index 0168bb7c..f5576edd 100644
--- a/src/routes/__layout.svelte
+++ b/src/routes/__layout.svelte
@@ -1,24 +1,9 @@
<script>
import '../app.css';
import { onMount } from 'svelte';
- import { rate, ws } from "$lib/store";
- import { messages } from "$lib/socket";
+ import { connect } from '$lib/socket';
- let token =
- 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImJvYiIsImlhdCI6MTY1ODc5NDkxMX0._zFTEADrbLffcXa5sIMrJtJ483VdIiYmsdt2ofibxu4';
-
- onMount(async () => {
- $ws = new WebSocket(`ws://localhost:3119/ws`);
-
- $ws.onmessage = (msg) => {
- let { type, data } = JSON.parse(msg.data);
- messages(data)[type] && messages(data)[type]();
- };
-
- $ws.onopen = () => {
- ws.send(JSON.stringify({ type: 'login', data: token }));
- };
- });
+ onMount(connect);
</script>
<slot />
diff --git a/src/routes/invoice.js b/src/routes/invoice.js
index fa493fe1..49bcfe70 100644
--- a/src/routes/invoice.js
+++ b/src/routes/invoice.js
@@ -10,11 +10,11 @@ export async function GET({ params }) {
})
.text();
- let body = await got
+ await got
.post('http://localhost:3119/invoice', {
json: { invoice: { text, network }, user: { username } }
})
.json();
- return { body };
+ return { body: { text } };
}
diff --git a/src/routes/invoice.svelte b/src/routes/invoice.svelte
index 6ea9e67f..40a66a24 100644
--- a/src/routes/invoice.svelte
+++ b/src/routes/invoice.svelte
@@ -1,13 +1,12 @@
<script>
- import { rate } from "$lib/store";
- import { onMount } from "svelte";
+ import { rate } from '$lib/store';
+ import { onMount } from 'svelte';
- onMount(() => {
+ export let text;
+</script>
- });
- </script>
+QR code
- QR code
-
- {$rate}
+{text}
+{$rate}
nt-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #080; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #080; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #080 } /* Keyword.Pseudo */ .highlight .kr { color: #080; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #00D; font-weight: bold } /* Literal.Number */ .highlight .s { color: #D20; background-color: #FFF0F0 } /* Literal.String */ .highlight .na { color: #369 } /* Name.Attribute */ .highlight .nb { color: #038 } /* Name.Builtin */ .highlight .nc { color: #B06; font-weight: bold } /* Name.Class */ .highlight .no { color: #036; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555 } /* Name.Decorator */ .highlight .ne { color: #B06; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #06B; font-weight: bold } /* Name.Function */ .highlight .nl { color: #369; font-style: italic } /* Name.Label */ .highlight .nn { color: #B06; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #369; font-weight: bold } /* Name.Property */ .highlight .nt { color: #B06; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #369 } /* Name.Variable */ .highlight .ow { color: #080 } /* Operator.Word */ .highlight .w { color: #BBB } /* Text.Whitespace */ .highlight .mb { color: #00D; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #00D; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #00D; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #00D; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #00D; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #D20; background-color: #FFF0F0 } /* Literal.String.Affix */ .highlight .sb { color: #D20; background-color: #FFF0F0 } /* Literal.String.Backtick */ .highlight .sc { color: #D20; background-color: #FFF0F0 } /* Literal.String.Char */ .highlight .dl { color: #D20; background-color: #FFF0F0 } /* Literal.String.Delimiter */ .highlight .sd { color: #D20; background-color: #FFF0F0 } /* Literal.String.Doc */ .highlight .s2 { color: #D20; background-color: #FFF0F0 } /* Literal.String.Double */ .highlight .se { color: #04D; background-color: #FFF0F0 } /* Literal.String.Escape */ .highlight .sh { color: #D20; background-color: #FFF0F0 } /* Literal.String.Heredoc */ .highlight .si { color: #33B; background-color: #FFF0F0 } /* Literal.String.Interpol */ .highlight .sx { color: #2B2; background-color: #F0FFF0 } /* Literal.String.Other */ .highlight .sr { color: #080; background-color: #FFF0FF } /* Literal.String.Regex */ .highlight .s1 { color: #D20; background-color: #FFF0F0 } /* Literal.String.Single */ .highlight .ss { color: #A60; background-color: #FFF0F0 } /* Literal.String.Symbol */ .highlight .bp { color: #038 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #06B; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #369 } /* Name.Variable.Class */ .highlight .vg { color: #D70 } /* Name.Variable.Global */ .highlight .vi { color: #33B } /* Name.Variable.Instance */ .highlight .vm { color: #369 } /* Name.Variable.Magic */ .highlight .il { color: #00D; font-weight: bold } /* Literal.Number.Integer.Long */
import config from "$config";
import db from "$db";
import { emit } from "$lib/sockets";
import store from "./store";
import bolt11 from "bolt11";
import { Op } from "@sequelize/core";
import {
  computeConversionFee,
  conversionFeeReceiver
} from "../routes/lightning/conversionFee";
import lnd from "$lib/lnd";
import ln from "$lib/ln";
import { payViaPaymentRequest } from "lightning";
import { l, warn, err } from "$lib/logging";

export default async (amount, memo, hash, user) => {
  l("attempting lightning payment", user.username, amount, hash);

  let payreq = bolt11.decode(hash);
  let routingInfo = payreq.tags.find(t => t.tagName === "routing_info");
  if (
    routingInfo &&
    routingInfo.data.find(d => d.fee_proportional_millionths > 10000)
  ) {
    warn("fee rate too high", d.fee_proportional_millionths);
    throw new Error(
      "Couldn't find a suitable route to pay to your destination"
    );
  }

  if (!amount || payreq.satoshis > amount) amount = payreq.satoshis;
  amount = parseInt(amount);

  if (store.seen.includes(hash)) {
    warn("attempted to pay a paid invoice", user.username);
    throw new Error("Invoice has been paid, can't pay again");
  }

  let error, fee, total, preimage, payment;

  let conversionFee = computeConversionFee(amount);
  let conversionFeeDeduction;
  await db.transaction(async transaction => {
    l("starting lightning send transaction");
    let account;
    if (user.account.asset === config.liquid.btcasset)
      account = await db.Account.findOne({
        where: {
          id: user.account.id,
          pubkey: null
        },
        lock: transaction.LOCK.UPDATE,
        transaction
      });
    else
      account = await db.Account.findOne({
        where: {
          user_id: user.id,
          asset: config.liquid.btcasset,
          pubkey: null,
          balance: {
            [Op.gte]: amount
          }
        },
        lock: transaction.LOCK.UPDATE,
        transaction
      });

    conversionFeeDeduction = Math.min(account.lightning_credits, conversionFee);
    total = amount + conversionFee - conversionFeeDeduction;

    if (!account || account.balance < total) {
      warn(
        "user attempting to send more than they have",
        user.id,
        amount,
        conversionFee
      );
      throw new Error("Insufficient funds");
    }

    await account.decrement(
      { balance: total, lightning_credits: conversionFeeDeduction },
      { transaction }
    );
    await account.reload({ transaction });

    l("debited account", account.id, total);
    let tokens;
    if (!payreq.satoshis) tokens = amount;

    let m;
    if (config.lna.lnd) {
      m = await payViaPaymentRequest({
        lnd,
        request: hash,
        max_fee: Math.min(10000, account.balance)
      });

      if (!m) throw new Error(`lightning payment failed ${amount} ${hash}`);
      if (m.payment_error) throw new Error(m.payment_error);
      ({ fee, secret, tokens } = m);
      preimage = secret.toString("hex");
    } else {
      let { msatoshi, msatoshi_sent, payment_preimage } = await ln.pay(
        hash,
        tokens ? `${tokens}sats` : undefined
      );
      preimage = payment_preimage;
      fee = parseInt((msatoshi_sent - msatoshi) / 1000);
    }

    if (store.seen.includes(preimage)) {
      warn("duplicate payment detected", preimage);
      throw new Error("Duplicate payment detected");
    }

    store.seen.push(preimage);
    store.seen.push(hash);
  });

  await db.transaction(async transaction => {
    l("starting lightning fee transaction");

    let account;
    if (user.account.asset === config.liquid.btcasset)
      account = await db.Account.findOne({
        where: {
          id: user.account.id,
          pubkey: null
        },
        lock: transaction.LOCK.UPDATE,
        transaction
      });
    else
      account = await db.Account.findOne({
        where: {
          user_id: user.id,
          asset: config.liquid.btcasset,
          pubkey: null,
          balance: {
            [Op.gte]: fee
          }
        },
        lock: transaction.LOCK.UPDATE,
        transaction
      });

    l("deducting fee", fee);
    await account.decrement({ balance: fee }, { transaction });
    await account.reload({ transaction });

    if (conversionFeeDeduction) {
      conversionFee -= conversionFeeDeduction;
    }

    l("conversion fee", conversionFee);

    let receiverAccount = await db.Account.findOne({
      where: {
        "$user.username$": conversionFeeReceiver
      },
      include: [
        {
          model: db.User,
          as: "user"
        }
      ],
      lock: transaction.LOCK.UPDATE,
      transaction
    });

    let fee_payment;
    let fee_payment_id = null;
    if (conversionFee) {
      await receiverAccount.increment(
        { balance: conversionFee },
        { transaction }
      );
      await receiverAccount.reload({ transaction });
      fee_payment = await db.Payment.create(
        {
          amount: conversionFee,
          fee: 0,
          memo: "Lightning conversion fee",
          account_id: receiverAccount.id,
          user_id: receiverAccount.user_id,
          rate: store.rates[receiverAccount.user.currency],
          currency: receiverAccount.user.currency,
          confirmed: true,
          received: true,
          network: "COINOS"
        },
        { transaction }
      );
      fee_payment_id = fee_payment.id;
    }

    payment = await db.Payment.create(
      {
        amount: -total,
        account_id: account.id,
        user_id: user.id,
        hash,
        memo,
        preimage,
        rate: store.rates[user.currency],
        currency: user.currency,
        confirmed: true,
        network: "lightning",
        fee,
        fee_payment_id
      },
      { transaction }
    );

    l("created payment record", payment.id);

    payment = payment.get({ plain: true });
    payment.account = account.get({ plain: true });
    payment.fee_payment = fee_payment && fee_payment.get({ plain: true });

    emit(user.username, "account", payment.account);
    emit(user.username, "payment", payment);

    l("sent lightning", user.username, amount, total, fee);
  });

  return payment;
};