partial withdrawl

This commit is contained in:
keyan 2021-05-12 20:51:37 -05:00
parent d92fc12187
commit 7a8afd56c3
7 changed files with 156 additions and 40 deletions

View File

@ -1,10 +1,14 @@
import { createInvoice, decodePaymentRequest, payViaPaymentRequest } from 'ln-service' import { createInvoice, decodePaymentRequest, subscribeToPayViaRequest } from 'ln-service'
import { UserInputError, AuthenticationError } from 'apollo-server-micro' import { UserInputError, AuthenticationError } from 'apollo-server-micro'
export default { export default {
Query: { Query: {
invoice: async (parent, { id }, { me, models, lnd }) => { invoice: async (parent, { id }, { me, models, lnd }) => {
return await models.invoice.findUnique({ where: { id: Number(id) } }) 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) } })
} }
}, },
@ -21,7 +25,12 @@ export default {
// set expires at to 3 hours into future // set expires at to 3 hours into future
const expiresAt = new Date(new Date().setHours(new Date().getHours() + 3)) const expiresAt = new Date(new Date().setHours(new Date().getHours() + 3))
const description = `${amount} sats for @${me.name} on stacker.news` const description = `${amount} sats for @${me.name} on stacker.news`
const invoice = await createInvoice({ description, lnd, tokens: amount, expires_at: expiresAt }) const invoice = await createInvoice({
description,
lnd,
tokens: amount,
expires_at: expiresAt
})
const data = { const data = {
hash: invoice.id, hash: invoice.id,
@ -46,24 +55,29 @@ export default {
const decoded = await decodePaymentRequest({ lnd, request: invoice }) const decoded = await decodePaymentRequest({ lnd, request: invoice })
// create withdrawl transactionally (id, bolt11, amount, fee) // create withdrawl transactionally (id, bolt11, amount, fee)
const withdrawl = const [withdrawl] =
await models.$queryRaw`SELECT confirm_withdrawl(${decoded.id}, ${invoice}, await models.$queryRaw`SELECT * FROM create_withdrawl(${decoded.id}, ${invoice},
${decoded.mtokens}, ${Number(maxFee)}, ${me.name})` ${Number(decoded.mtokens)}, ${Number(maxFee)}, ${me.name})`
// create the payment, subscribing to its status // create the payment, subscribing to its status
const sub = subscribeToPayViaRequest({ lnd, request: invoice, max_fee_mtokens: maxFee, pathfinding_timeout: 30000 }) const sub = subscribeToPayViaRequest({
lnd,
request: invoice,
max_fee_mtokens: maxFee,
pathfinding_timeout: 30000
})
// if it's confirmed, update confirmed // if it's confirmed, update confirmed
sub.on('confirmed', recordStatus) sub.on('confirmed', console.log)
// if the payment fails, we need to // if the payment fails, we need to
// 1. transactionally return the funds to the user // 1. transactionally return the funds to the user
// 2. transactionally update the widthdrawl as failed // 2. transactionally update the widthdrawl as failed
sub.on('failed', recordStatus) sub.on('failed', console.log)
// in walletd // in walletd
// for each payment that hasn't failed or succeede // for each payment that hasn't failed or succeede
return 0 return withdrawl
} }
} }
} }

View File

@ -3,11 +3,12 @@ import { gql } from 'apollo-server-micro'
export default gql` export default gql`
extend type Query { extend type Query {
invoice(id: ID!): Invoice! invoice(id: ID!): Invoice!
withdrawl(id: ID!): Withdrawl!
} }
extend type Mutation { extend type Mutation {
createInvoice(amount: Int!): Invoice! createInvoice(amount: Int!): Invoice!
createWithdrawl(invoice: String!, maxFee: Int!): Int createWithdrawl(invoice: String!, maxFee: Int!): Withdrawl!
} }
type Invoice { type Invoice {
@ -19,4 +20,16 @@ export default gql`
confirmedAt: String confirmedAt: String
msatsReceived: Int msatsReceived: Int
} }
type Withdrawl {
id: ID!
createdAt: String!
hash: String!
bolt11: String!
msatsPaying: Int!
msatsPaid: Int
msatsFeePaying: Int!
msatsFeePaid: Int
status: String!
}
` `

View File

@ -40,7 +40,8 @@ export function WalletForm () {
} }
export const FundSchema = Yup.object({ export const FundSchema = Yup.object({
amount: Yup.number('must be a number').required('required').positive('must be positive').integer('must be whole') amount: Yup.number().typeError('must be a number').required('required')
.positive('must be positive').integer('must be whole')
}) })
export function FundForm () { export function FundForm () {
@ -81,10 +82,12 @@ export function FundForm () {
export const WithdrawlSchema = Yup.object({ export const WithdrawlSchema = Yup.object({
invoice: Yup.string().required('required'), invoice: Yup.string().required('required'),
maxFee: Yup.number('must be a number').required('required').positive('must be positive').integer('must be whole') maxFee: Yup.number().typeError('must be a number').required('required')
.min(0, 'must be positive').integer('must be whole')
}) })
export function WithdrawlForm () { export function WithdrawlForm () {
const router = useRouter()
const query = gql` const query = gql`
{ {
me { me {
@ -95,7 +98,9 @@ export function WithdrawlForm () {
const [createWithdrawl] = useMutation(gql` const [createWithdrawl] = useMutation(gql`
mutation createWithdrawl($invoice: String!, $maxFee: Int!) { mutation createWithdrawl($invoice: String!, $maxFee: Int!) {
createWithdrawl(invoice: $invoice, maxFee: $maxFee) createWithdrawl(invoice: $invoice, maxFee: $maxFee) {
id
}
}`) }`)
return ( return (
@ -106,13 +111,13 @@ export function WithdrawlForm () {
<Form <Form
className='pt-3' className='pt-3'
initial={{ initial={{
destination: '', invoice: '',
maxFee: 0, maxFee: 0
amount: 0
}} }}
schema={WithdrawlSchema} schema={WithdrawlSchema}
onSubmit={async ({ invoice, maxFee }) => { onSubmit={async ({ invoice, maxFee }) => {
await createWithdrawl({ variables: { invoice, maxFee: Number(maxFee) } }) const { data } = await createWithdrawl({ variables: { invoice, maxFee: Number(maxFee) } })
router.push(`/withdrawls/${data.createWithdrawl.id}`)
}} }}
> >
<Input <Input

35
pages/withdrawls/[id].js Normal file
View File

@ -0,0 +1,35 @@
import { useQuery } from '@apollo/client'
import gql from 'graphql-tag'
import LayoutCenter from '../../components/layout-center'
export async function getServerSideProps ({ params: { id } }) {
return {
props: {
id
}
}
}
export default function Withdrawl ({ id }) {
const query = gql`
{
withdrawl(id: ${id}) {
bolt11
}
}`
return (
<LayoutCenter>
<LoadWithdrawl query={query} />
</LayoutCenter>
)
}
function LoadWithdrawl ({ query }) {
const { loading, error, data } = useQuery(query, { pollInterval: 1000 })
if (error) return <div>error</div>
if (!data || loading) {
return <div>withdrawl loading</div>
}
return <div>hi</div>
}

View File

@ -65,27 +65,4 @@ BEGIN
END IF; END IF;
RETURN 0; RETURN 0;
END; END;
$$;
CREATE OR REPLACE FUNCTION create_withdrawl(lnd_id TEXT, bolt11 TEXT, msats_amount INTEGER, msats_max_fee INTEGER, username TEXT)
RETURNS INTEGER
LANGUAGE plpgsql
AS $$
DECLARE
user_id INTEGER;
user_msats INTEGER;
withdrawl "Withdrawl";
BEGIN
SELECT msats, id INTO user_msats, user_id FROM users WHERE name = username;
IF msats_amount + msats_max_fee > user_msats THEN
RAISE EXCEPTION 'SN_INSUFFICIENT_FUNDS';
END IF;
INSERT INTO "Withdrawl" (hash, bolt11, "msatsPaying", "msatsFeePaying", "userId", updated_at)
VALUES (lnd_id, bolt11, msats_amount, msats_max_fee, user_id, 'now') RETURNING * INTO withdrawl;
UPDATE users SET msats = msats - msats_amount - msats_max_fee WHERE id = user_id;
RETURN withdrawl;
END;
$$; $$;

View File

@ -0,0 +1,28 @@
-- CreateEnum
CREATE TYPE "WithdrawlStatus" AS ENUM ('INSUFFICIENT_BALANCE', 'INVALID_PAYMENT', 'PATHFINDING_TIMEOUT', 'ROUTE_NOT_FOUND');
-- CreateTable
CREATE TABLE "Withdrawl" (
"id" SERIAL NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"userId" INTEGER NOT NULL,
"hash" TEXT NOT NULL,
"bolt11" TEXT NOT NULL,
"msatsPaying" INTEGER NOT NULL,
"msatsPaid" INTEGER,
"msatsFeePaying" INTEGER NOT NULL,
"msatsFeePaid" INTEGER,
"status" "WithdrawlStatus",
PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Withdrawl.hash_unique" ON "Withdrawl"("hash");
-- CreateIndex
CREATE INDEX "Withdrawl.userId_index" ON "Withdrawl"("userId");
-- AddForeignKey
ALTER TABLE "Withdrawl" ADD FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -0,0 +1,44 @@
CREATE OR REPLACE FUNCTION create_withdrawl(lnd_id TEXT, invoice TEXT, msats_amount INTEGER, msats_max_fee INTEGER, username TEXT)
RETURNS "Withdrawl"
LANGUAGE plpgsql
AS $$
DECLARE
user_id INTEGER;
user_msats INTEGER;
withdrawl "Withdrawl";
BEGIN
SELECT msats, id INTO user_msats, user_id FROM users WHERE name = username;
IF (msats_amount + msats_max_fee) > user_msats THEN
RAISE EXCEPTION 'SN_INSUFFICIENT_FUNDS';
END IF;
INSERT INTO "Withdrawl" (hash, bolt11, "msatsPaying", "msatsFeePaying", "userId", updated_at)
VALUES (lnd_id, invoice, msats_amount, msats_max_fee, user_id, 'now') RETURNING * INTO withdrawl;
UPDATE users SET msats = msats - msats_amount - msats_max_fee WHERE id = user_id;
RETURN withdrawl;
END;
$$;
CREATE OR REPLACE FUNCTION confirm_withdrawl(lnd_id TEXT, msats_paid INTEGER, msats_fee_paid INTEGER)
RETURNS INTEGER
LANGUAGE plpgsql
AS $$
DECLARE
BEGIN
END;
$$;
CREATE OR REPLACE FUNCTION reverse_withdrawl(lnd_id TEXT, msats_paid INTEGER, msats_fee_paid INTEGER)
RETURNS INTEGER
LANGUAGE plpgsql
AS $$
DECLARE
BEGIN
END;
$$;