make withdrawls mostly work
This commit is contained in:
parent
ce55fdfe9c
commit
157488ea5d
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
`
|
||||
|
|
|
@ -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 (
|
||||
<Formik
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Moon from '../svgs/moon-fill.svg'
|
||||
import Check from '../svgs/check-double-line.svg'
|
||||
import Fail from '../svgs/close-line.svg'
|
||||
import ThumbDown from '../svgs/thumb-down-fill.svg'
|
||||
|
||||
function InvoiceDefaultStatus ({ status }) {
|
||||
return (
|
||||
|
@ -23,7 +23,7 @@ function InvoiceConfirmedStatus ({ status }) {
|
|||
function InvoiceFailedStatus ({ status }) {
|
||||
return (
|
||||
<div className='d-flex mt-2'>
|
||||
<Fail className='fill-danger' />
|
||||
<ThumbDown className='fill-danger' />
|
||||
<div className='ml-3 text-danger' style={{ fontWeight: '600' }}>{status}</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <InvoiceSkeleton status='generating' />
|
||||
}
|
||||
|
||||
|
@ -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 <WithdrawlSkeleton status='sending' />
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className={`${data ?? 'invisible'} text-success pb-5`}>
|
||||
you have <span className='text-monospace'>{data && data.me.msats}</span> millisats
|
||||
<h2 className={`${data ? 'visible' : 'invisible'} text-success pb-5`}>
|
||||
you have <span className='text-monospace'>{data && data.me.sats}</span> sats
|
||||
</h2>
|
||||
<Form
|
||||
className='pt-3'
|
||||
|
@ -115,8 +120,10 @@ export function WithdrawlForm () {
|
|||
invoice: '',
|
||||
maxFee: 0
|
||||
}}
|
||||
initialError={error ? error.toString() : undefined}
|
||||
schema={WithdrawlSchema}
|
||||
onSubmit={async ({ invoice, maxFee }) => {
|
||||
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={<InputGroup.Text className='text-monospace'>millisats</InputGroup.Text>}
|
||||
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
||||
/>
|
||||
<SubmitButton variant='success' className='mt-2'>withdrawl</SubmitButton>
|
||||
</Form>
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
<div className='w-100'>
|
||||
<InputSkeleton label='invoice' />
|
||||
</div>
|
||||
<div className='w-100'>
|
||||
<InputSkeleton label='max fee' />
|
||||
</div>
|
||||
<InvoiceStatus status={status} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function LoadWithdrawl ({ query }) {
|
||||
const { loading, error, data } = useQuery(query, { pollInterval: 1000 })
|
||||
if (error) return <div>error</div>
|
||||
if (!data || loading) {
|
||||
return (
|
||||
<>
|
||||
<div className='w-100'>
|
||||
<InputSkeleton label='invoice' />
|
||||
</div>
|
||||
<div className='w-100'>
|
||||
<InputSkeleton label='max fee' />
|
||||
</div>
|
||||
<InvoiceStatus status='pending' />
|
||||
</>
|
||||
)
|
||||
return <WithdrawlSkeleton status='loading' />
|
||||
}
|
||||
|
||||
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 <small className='ml-3'>contact keyan!</small></>
|
||||
variant = 'failed'
|
||||
break
|
||||
case 'INVALID_PAYMENT':
|
||||
status = 'invalid invoice'
|
||||
variant = 'failed'
|
||||
break
|
||||
case 'PATHFINDING_TIMEOUT':
|
||||
status = <>timed out trying to find route <small className='ml-3'>try increasing max fee</small></>
|
||||
variant = 'failed'
|
||||
break
|
||||
case 'ROUTE_NOT_FOUND':
|
||||
status = <>could not find route <small className='ml-3'>try increasing max fee</small></>
|
||||
variant = 'failed'
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -57,11 +90,11 @@ function LoadWithdrawl ({ query }) {
|
|||
<div className='w-100'>
|
||||
<Input
|
||||
label='max fee' type='text'
|
||||
placeholder={data.withdrawl.msatsFeePaying} readOnly
|
||||
append={<InputGroup.Text className='text-monospace'>millisats</InputGroup.Text>}
|
||||
placeholder={data.withdrawl.satsFeePaying} readOnly
|
||||
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
||||
/>
|
||||
</div>
|
||||
<InvoiceStatus status='pending' />
|
||||
<InvoiceStatus variant={variant} status={status} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
$$;
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterEnum
|
||||
ALTER TYPE "WithdrawlStatus" ADD VALUE 'CONFIRMED';
|
|
@ -88,10 +88,12 @@ model Invoice {
|
|||
}
|
||||
|
||||
enum WithdrawlStatus {
|
||||
CONFIRMED
|
||||
INSUFFICIENT_BALANCE
|
||||
INVALID_PAYMENT
|
||||
PATHFINDING_TIMEOUT
|
||||
ROUTE_NOT_FOUND
|
||||
UNKNOWN_FAILURE
|
||||
}
|
||||
|
||||
model Withdrawl {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M22 15h-3V3h3a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zm-5.293 1.293l-6.4 6.4a.5.5 0 0 1-.654.047L8.8 22.1a1.5 1.5 0 0 1-.553-1.57L9.4 16H3a2 2 0 0 1-2-2v-2.104a2 2 0 0 1 .15-.762L4.246 3.62A1 1 0 0 1 5.17 3H16a1 1 0 0 1 1 1v11.586a1 1 0 0 1-.293.707z"/></svg>
|
After Width: | Height: | Size: 379 B |
Loading…
Reference in New Issue