diff --git a/api/resolvers/wallet.js b/api/resolvers/wallet.js index e15d7e12..b13f5712 100644 --- a/api/resolvers/wallet.js +++ b/api/resolvers/wallet.js @@ -7,7 +7,6 @@ export default { return await models.invoice.findUnique({ where: { id: Number(id) } }) }, withdrawl: async (parent, { id }, { me, models, lnd }) => { - console.log(models) return await models.withdrawl.findUnique({ where: { id: Number(id) } }) } }, @@ -54,30 +53,64 @@ export default { // decode invoice to get amount const decoded = await decodePaymentRequest({ lnd, request: invoice }) + const msatsFee = Number(maxFee) * 1000 // create withdrawl transactionally (id, bolt11, amount, fee) - const [withdrawl] = + try { + const [withdrawl] = await models.$queryRaw`SELECT * FROM create_withdrawl(${decoded.id}, ${invoice}, - ${Number(decoded.mtokens)}, ${Number(maxFee)}, ${me.name})` + ${Number(decoded.mtokens)}, ${msatsFee}, ${me.name})` - // create the payment, subscribing to its status - const sub = subscribeToPayViaRequest({ - lnd, - request: invoice, - max_fee_mtokens: maxFee, - pathfinding_timeout: 30000 - }) + // create the payment, subscribing to its status + const sub = subscribeToPayViaRequest({ + lnd, + request: invoice, + // can't use max_fee_mtokens https://github.com/alexbosworth/ln-service/issues/141 + max_fee: Number(maxFee), + pathfinding_timeout: 30000 + }) - // if it's confirmed, update confirmed - sub.on('confirmed', console.log) + // if it's confirmed, update confirmed returning extra fees to user + sub.on('confirmed', async e => { + console.log(e) + // mtokens also contains the fee + const fee = Number(e.fee_mtokens) + const paid = Number(e.mtokens) - fee + await models.$queryRaw`SELECT confirm_withdrawl(${withdrawl.id}, ${paid}, ${fee})` + }) - // if the payment fails, we need to - // 1. transactionally return the funds to the user - // 2. transactionally update the widthdrawl as failed - sub.on('failed', console.log) + // if the payment fails, we need to + // 1. return the funds to the user + // 2. update the widthdrawl as failed + sub.on('failed', async e => { + console.log(e) + let status = 'UNKNOWN_FAILURE' + if (e.is_insufficient_balance) { + status = 'INSUFFICIENT_BALANCE' + } else if (e.is_invalid_payment) { + status = 'INVALID_PAYMENT' + } else if (e.is_pathfinding_timeout) { + status = 'PATHFINDING_TIMEOUT' + } else if (e.is_route_not_found) { + status = 'ROUTE_NOT_FOUND' + } + await models.$queryRaw`SELECT reverse_withdrawl(${withdrawl.id}, ${status})` + }) - // in walletd - // for each payment that hasn't failed or succeede - return withdrawl + return withdrawl + } catch (error) { + const { meta: { message } } = error + if (message.includes('SN_INSUFFICIENT_FUNDS')) { + throw new UserInputError('insufficient funds') + } + throw error + } } + }, + + Withdrawl: { + satsPaying: w => Math.floor(w.msatsPaying / 1000), + satsPaid: w => Math.floor(w.msatsPaid / 1000), + satsFeePaying: w => Math.floor(w.msatsFeePaying / 1000), + satsFeePaid: w => Math.floor(w.msatsFeePaid / 1000) } } diff --git a/api/typeDefs/wallet.js b/api/typeDefs/wallet.js index 18123cc6..a799dc57 100644 --- a/api/typeDefs/wallet.js +++ b/api/typeDefs/wallet.js @@ -27,9 +27,13 @@ export default gql` hash: String! bolt11: String! msatsPaying: Int! + satsPaying: Int! msatsPaid: Int + satsPaid: Int msatsFeePaying: Int! + satsFeePaying: Int! msatsFeePaid: Int + satsFeePaid: Int status: String } ` diff --git a/components/form.js b/components/form.js index 1a87b12f..b550a059 100644 --- a/components/form.js +++ b/components/form.js @@ -83,9 +83,9 @@ export function Input ({ label, prepend, append, hint, ...props }) { } export function Form ({ - initial, schema, onSubmit, children, ...props + initial, schema, onSubmit, children, initialError, ...props }) { - const [error, setError] = useState() + const [error, setError] = useState(initialError) return ( - +
{status}
) diff --git a/invoiced/index.js b/invoiced/index.js index 05fb17c8..877f68fe 100644 --- a/invoiced/index.js +++ b/invoiced/index.js @@ -60,3 +60,8 @@ async function checkPending () { } checkPending() + +// TODO +// in walletd +// for each payment that hasn't failed or succeeded after 30 seconds after creation +// request status from lnd and record diff --git a/pages/wallet.js b/pages/wallet.js index 3d6dbb1b..93a2bc5a 100644 --- a/pages/wallet.js +++ b/pages/wallet.js @@ -7,6 +7,7 @@ import { gql, useMutation, useQuery } from '@apollo/client' import { InvoiceSkeleton } from '../components/invoice' import LayoutCenter from '../components/layout-center' import InputGroup from 'react-bootstrap/InputGroup' +import { WithdrawlSkeleton } from './withdrawls/[id]' export default function Wallet () { return ( @@ -47,14 +48,14 @@ export const FundSchema = Yup.object({ export function FundForm () { const router = useRouter() - const [createInvoice, { called }] = useMutation(gql` + const [createInvoice, { called, error }] = useMutation(gql` mutation createInvoice($amount: Int!) { createInvoice(amount: $amount) { id } }`) - if (called) { + if (called && !error) { return } @@ -92,22 +93,26 @@ export function WithdrawlForm () { const query = gql` { me { - msats + sats } }` const { data } = useQuery(query, { pollInterval: 1000 }) - const [createWithdrawl] = useMutation(gql` + const [createWithdrawl, { called, error }] = useMutation(gql` mutation createWithdrawl($invoice: String!, $maxFee: Int!) { createWithdrawl(invoice: $invoice, maxFee: $maxFee) { id } }`) + if (called && !error) { + return + } + return ( <> -

- you have {data && data.me.msats} millisats +

+ you have {data && data.me.sats} sats

{ + console.log('calling') const { data } = await createWithdrawl({ variables: { invoice, maxFee: Number(maxFee) } }) router.push(`/withdrawls/${data.createWithdrawl.id}`) }} @@ -131,7 +138,7 @@ export function WithdrawlForm () { label='max fee' name='maxFee' required - append={millisats} + append={sats} /> withdrawl
diff --git a/pages/withdrawls/[id].js b/pages/withdrawls/[id].js index 8315ebe6..c4a3c2bc 100644 --- a/pages/withdrawls/[id].js +++ b/pages/withdrawls/[id].js @@ -18,7 +18,9 @@ export default function Withdrawl ({ id }) { { withdrawl(id: ${id}) { bolt11 - msatsFeePaying + satsPaid + satsFeePaying + satsFeePaid status } }` @@ -29,21 +31,52 @@ export default function Withdrawl ({ id }) { ) } +export function WithdrawlSkeleton ({ status }) { + return ( + <> +
+ +
+
+ +
+ + + ) +} + function LoadWithdrawl ({ query }) { const { loading, error, data } = useQuery(query, { pollInterval: 1000 }) if (error) return
error
if (!data || loading) { - return ( - <> -
- -
-
- -
- - - ) + return + } + + let status = 'pending' + let variant = 'default' + switch (data.withdrawl.status) { + case 'CONFIRMED': + status = `sent ${data.withdrawl.satsPaid} sats with ${data.withdrawl.satsFeePaid} sats in routing fees` + variant = 'confirmed' + break + case 'INSUFFICIENT_BALANCE': + status = <>insufficient balance contact keyan! + variant = 'failed' + break + case 'INVALID_PAYMENT': + status = 'invalid invoice' + variant = 'failed' + break + case 'PATHFINDING_TIMEOUT': + status = <>timed out trying to find route try increasing max fee + variant = 'failed' + break + case 'ROUTE_NOT_FOUND': + status = <>could not find route try increasing max fee + variant = 'failed' + break + default: + break } return ( @@ -57,11 +90,11 @@ function LoadWithdrawl ({ query }) {
millisats} + placeholder={data.withdrawl.satsFeePaying} readOnly + append={sats} />
- + ) } diff --git a/prisma/migrations/20210511170231_vote/migration.sql b/prisma/migrations/20210511170231_vote/migration.sql index 48f14281..931d0274 100644 --- a/prisma/migrations/20210511170231_vote/migration.sql +++ b/prisma/migrations/20210511170231_vote/migration.sql @@ -18,8 +18,11 @@ BEGIN IF EXISTS (SELECT 1 FROM "Vote" WHERE "itemId" = item_id AND "userId" = user_id) THEN INSERT INTO "Vote" (sats, "itemId", "userId", boost, updated_at) VALUES (vote_sats, item_id, user_id, true, 'now'); ELSE - INSERT INTO "Vote" (sats, "itemId", "userId", updated_at) VALUES (vote_sats, item_id, user_id, 'now'); - UPDATE users SET msats = msats + (vote_sats * 1000) WHERE id = (SELECT "userId" FROM "Item" WHERE id = item_id); + INSERT INTO "Vote" (sats, "itemId", "userId", updated_at) VALUES (1, item_id, user_id, 'now'); + UPDATE users SET msats = msats + 1000 WHERE id = (SELECT "userId" FROM "Item" WHERE id = item_id); + IF vote_sats > 1 THEN + INSERT INTO "Vote" (sats, "itemId", "userId", boost, updated_at) VALUES (vote_sats - 1, item_id, user_id, true, 'now'); + END IF; END IF; RETURN vote_sats; diff --git a/prisma/migrations/20210513002503_withdrawl_funcs/migration.sql b/prisma/migrations/20210513002503_withdrawl_funcs/migration.sql index 2d96c978..82bbef3a 100644 --- a/prisma/migrations/20210513002503_withdrawl_funcs/migration.sql +++ b/prisma/migrations/20210513002503_withdrawl_funcs/migration.sql @@ -21,24 +21,38 @@ BEGIN END; $$; -CREATE OR REPLACE FUNCTION confirm_withdrawl(lnd_id TEXT, msats_paid INTEGER, msats_fee_paid INTEGER) +CREATE OR REPLACE FUNCTION confirm_withdrawl(wid INTEGER, msats_paid INTEGER, msats_fee_paid INTEGER) RETURNS INTEGER LANGUAGE plpgsql AS $$ DECLARE - + msats_fee_paying INTEGER; + user_id INTEGER; BEGIN + IF EXISTS (SELECT 1 FROM "Withdrawl" WHERE id = wid AND status IS NULL) THEN + UPDATE "Withdrawl" SET status = 'CONFIRMED', "msatsPaid" = msats_paid, "msatsFeePaid" = msats_fee_paid WHERE id = wid; + SELECT "msatsFeePaying", "userId" INTO msats_fee_paying, user_id FROM "Withdrawl" WHERE id = wid; + UPDATE users SET msats = msats + (msats_fee_paying - msats_fee_paid) WHERE id = user_id; + END IF; + RETURN 0; END; $$; -CREATE OR REPLACE FUNCTION reverse_withdrawl(lnd_id TEXT, msats_paid INTEGER, msats_fee_paid INTEGER) +CREATE OR REPLACE FUNCTION reverse_withdrawl(wid INTEGER, wstatus "WithdrawlStatus") RETURNS INTEGER LANGUAGE plpgsql AS $$ DECLARE - + msats_fee_paying INTEGER; + msats_paying INTEGER; + user_id INTEGER; BEGIN - + IF EXISTS (SELECT 1 FROM "Withdrawl" WHERE id = wid AND status IS NULL) THEN + UPDATE "Withdrawl" SET status = wstatus WHERE id = wid; + SELECT "msatsPaying", "msatsFeePaying", "userId" INTO msats_paying, msats_fee_paying, user_id FROM "Withdrawl" WHERE id = wid; + UPDATE users SET msats = msats + msats_paying + msats_fee_paying WHERE id = user_id; + END IF; + RETURN 0; END; $$; \ No newline at end of file diff --git a/prisma/migrations/20210513191840_enum/migration.sql b/prisma/migrations/20210513191840_enum/migration.sql new file mode 100644 index 00000000..ddc333d7 --- /dev/null +++ b/prisma/migrations/20210513191840_enum/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "WithdrawlStatus" ADD VALUE 'CONFIRMED'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 306653e1..8196312a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -88,10 +88,12 @@ model Invoice { } enum WithdrawlStatus { + CONFIRMED INSUFFICIENT_BALANCE INVALID_PAYMENT PATHFINDING_TIMEOUT ROUTE_NOT_FOUND + UNKNOWN_FAILURE } model Withdrawl { diff --git a/svgs/thumb-down-fill.svg b/svgs/thumb-down-fill.svg new file mode 100644 index 00000000..fb815ea1 --- /dev/null +++ b/svgs/thumb-down-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file