Add invoice HMAC
This prevents entities which know the invoice hash (like all LN nodes on the payment path) from using the invoice hash on SN. Only the user which created the invoice knows the HMAC and thus can use the invoice hash.
This commit is contained in:
parent
4fe1d416de
commit
bb2212d51e
|
@ -47,6 +47,7 @@ PUBLIC_URL=http://localhost:3000
|
||||||
LND_CONNECT_ADDRESS=03cc1d0932bb99b0697f5b5e5961b83ab7fd66f1efc4c9f5c7bad66c1bcbe78f02@xhlmkj7mfrl6ejnczfwl2vqik3xim6wzmurc2vlyfoqw2sasaocgpuad.onion:9735
|
LND_CONNECT_ADDRESS=03cc1d0932bb99b0697f5b5e5961b83ab7fd66f1efc4c9f5c7bad66c1bcbe78f02@xhlmkj7mfrl6ejnczfwl2vqik3xim6wzmurc2vlyfoqw2sasaocgpuad.onion:9735
|
||||||
NEXTAUTH_SECRET=3_0W_PhDRZVanbeJsZZGIEljexkKoGbL6qGIqSwTjjI
|
NEXTAUTH_SECRET=3_0W_PhDRZVanbeJsZZGIEljexkKoGbL6qGIqSwTjjI
|
||||||
JWT_SIGNING_PRIVATE_KEY={"kty":"oct","kid":"FvD__hmeKoKHu2fKjUrWbRKfhjimIM4IKshyrJG4KSM","alg":"HS512","k":"3_0W_PhDRZVanbeJsZZGIEljexkKoGbL6qGIqSwTjjI"}
|
JWT_SIGNING_PRIVATE_KEY={"kty":"oct","kid":"FvD__hmeKoKHu2fKjUrWbRKfhjimIM4IKshyrJG4KSM","alg":"HS512","k":"3_0W_PhDRZVanbeJsZZGIEljexkKoGbL6qGIqSwTjjI"}
|
||||||
|
INVOICE_HMAC_KEY=a4c1d9c81edb87b79d28809876a18cf72293eadb39f92f3f4f2f1cfbdf907c91
|
||||||
|
|
||||||
# imgproxy
|
# imgproxy
|
||||||
NEXT_PUBLIC_IMGPROXY_URL=
|
NEXT_PUBLIC_IMGPROXY_URL=
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { amountSchema, bountySchema, commentSchema, discussionSchema, jobSchema,
|
||||||
import { sendUserNotification } from '../webPush'
|
import { sendUserNotification } from '../webPush'
|
||||||
import { proxyImages } from './imgproxy'
|
import { proxyImages } from './imgproxy'
|
||||||
import { defaultCommentSort } from '../../lib/item'
|
import { defaultCommentSort } from '../../lib/item'
|
||||||
|
import { createHmac } from './wallet'
|
||||||
|
|
||||||
export async function commentFilterClause (me, models) {
|
export async function commentFilterClause (me, models) {
|
||||||
let clause = ` AND ("Item"."weightedVotes" - "Item"."weightedDownVotes" > -${ITEM_FILTER_THRESHOLD}`
|
let clause = ` AND ("Item"."weightedVotes" - "Item"."weightedDownVotes" > -${ITEM_FILTER_THRESHOLD}`
|
||||||
|
@ -37,9 +38,17 @@ export async function commentFilterClause (me, models) {
|
||||||
return clause
|
return clause
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkInvoice (models, invoiceHash, fee) {
|
async function checkInvoice (models, hash, hmac, fee) {
|
||||||
|
if (!hmac) {
|
||||||
|
throw new GraphQLError('hmac required', { extensions: { code: 'BAD_INPUT' } })
|
||||||
|
}
|
||||||
|
const hmac2 = createHmac(hash)
|
||||||
|
if (hmac !== hmac2) {
|
||||||
|
throw new GraphQLError('bad hmac', { extensions: { code: 'FORBIDDEN' } })
|
||||||
|
}
|
||||||
|
|
||||||
const invoice = await models.invoice.findUnique({
|
const invoice = await models.invoice.findUnique({
|
||||||
where: { hash: invoiceHash },
|
where: { hash },
|
||||||
include: {
|
include: {
|
||||||
user: true
|
user: true
|
||||||
}
|
}
|
||||||
|
@ -590,7 +599,7 @@ export default {
|
||||||
if (id) {
|
if (id) {
|
||||||
return await updateItem(parent, { id, data }, { me, models })
|
return await updateItem(parent, { id, data }, { me, models })
|
||||||
} else {
|
} else {
|
||||||
return await createItem(parent, data, { me, models, invoiceHash: args.invoiceHash })
|
return await createItem(parent, data, { me, models, invoiceHash: args.invoiceHash, invoiceHmac: args.invoiceHmac })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
upsertDiscussion: async (parent, args, { me, models }) => {
|
upsertDiscussion: async (parent, args, { me, models }) => {
|
||||||
|
@ -601,7 +610,7 @@ export default {
|
||||||
if (id) {
|
if (id) {
|
||||||
return await updateItem(parent, { id, data }, { me, models })
|
return await updateItem(parent, { id, data }, { me, models })
|
||||||
} else {
|
} else {
|
||||||
return await createItem(parent, data, { me, models, invoiceHash: args.invoiceHash })
|
return await createItem(parent, data, { me, models, invoiceHash: args.invoiceHash, invoiceHmac: args.invoiceHmac })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
upsertBounty: async (parent, args, { me, models }) => {
|
upsertBounty: async (parent, args, { me, models }) => {
|
||||||
|
@ -616,11 +625,11 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
upsertPoll: async (parent, { id, ...data }, { me, models }) => {
|
upsertPoll: async (parent, { id, ...data }, { me, models }) => {
|
||||||
const { sub, forward, boost, title, text, options, invoiceHash } = data
|
const { sub, forward, boost, title, text, options, invoiceHash, invoiceHmac } = data
|
||||||
let author = me
|
let author = me
|
||||||
const trx = []
|
const trx = []
|
||||||
if (!me && invoiceHash) {
|
if (!me && invoiceHash) {
|
||||||
const invoice = await checkInvoice(models, invoiceHash, ANON_POST_FEE)
|
const invoice = await checkInvoice(models, invoiceHash, invoiceHmac, ANON_POST_FEE)
|
||||||
author = invoice.user
|
author = invoice.user
|
||||||
trx.push(models.invoice.delete({ where: { hash: invoiceHash } }))
|
trx.push(models.invoice.delete({ where: { hash: invoiceHash } }))
|
||||||
}
|
}
|
||||||
|
@ -707,7 +716,7 @@ export default {
|
||||||
},
|
},
|
||||||
createComment: async (parent, data, { me, models }) => {
|
createComment: async (parent, data, { me, models }) => {
|
||||||
await ssValidate(commentSchema, data)
|
await ssValidate(commentSchema, data)
|
||||||
const item = await createItem(parent, data, { me, models, invoiceHash: data.invoiceHash })
|
const item = await createItem(parent, data, { me, models, invoiceHash: data.invoiceHash, invoiceHmac: data.invoiceHmac })
|
||||||
// fetch user to get up-to-date name
|
// fetch user to get up-to-date name
|
||||||
const user = await models.user.findUnique({ where: { id: me?.id || ANON_USER_ID } })
|
const user = await models.user.findUnique({ where: { id: me?.id || ANON_USER_ID } })
|
||||||
|
|
||||||
|
@ -740,7 +749,7 @@ export default {
|
||||||
|
|
||||||
return id
|
return id
|
||||||
},
|
},
|
||||||
act: async (parent, { id, sats, invoiceHash }, { me, models }) => {
|
act: async (parent, { id, sats, invoiceHash, invoiceHmac }, { me, models }) => {
|
||||||
// need to make sure we are logged in
|
// need to make sure we are logged in
|
||||||
if (!me && !invoiceHash) {
|
if (!me && !invoiceHash) {
|
||||||
throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } })
|
throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } })
|
||||||
|
@ -749,8 +758,9 @@ export default {
|
||||||
await ssValidate(amountSchema, { amount: sats })
|
await ssValidate(amountSchema, { amount: sats })
|
||||||
|
|
||||||
let user = me
|
let user = me
|
||||||
|
let invoice
|
||||||
if (!me && invoiceHash) {
|
if (!me && invoiceHash) {
|
||||||
const invoice = await checkInvoice(models, invoiceHash, sats)
|
invoice = await checkInvoice(models, invoiceHash, invoiceHmac, sats)
|
||||||
user = invoice.user
|
user = invoice.user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -766,8 +776,8 @@ export default {
|
||||||
const calls = [
|
const calls = [
|
||||||
models.$queryRaw`SELECT item_act(${Number(id)}::INTEGER, ${user.id}::INTEGER, 'TIP', ${Number(sats)}::INTEGER)`
|
models.$queryRaw`SELECT item_act(${Number(id)}::INTEGER, ${user.id}::INTEGER, 'TIP', ${Number(sats)}::INTEGER)`
|
||||||
]
|
]
|
||||||
if (!me && invoiceHash) {
|
if (invoice) {
|
||||||
calls.push(models.invoice.delete({ where: { hash: invoiceHash } }))
|
calls.push(models.invoice.delete({ where: { hash: invoice.hash } }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const [{ item_act: vote }] = await serialize(models, ...calls)
|
const [{ item_act: vote }] = await serialize(models, ...calls)
|
||||||
|
@ -1093,11 +1103,11 @@ export const updateItem = async (parent, { id, data: { sub, title, url, text, bo
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
const createItem = async (parent, { sub, title, url, text, boost, forward, bounty, parentId }, { me, models, invoiceHash }) => {
|
const createItem = async (parent, { sub, title, url, text, boost, forward, bounty, parentId }, { me, models, invoiceHash, invoiceHmac }) => {
|
||||||
let author = me
|
let author = me
|
||||||
const trx = []
|
const trx = []
|
||||||
if (!me && invoiceHash) {
|
if (!me && invoiceHash) {
|
||||||
const invoice = await checkInvoice(models, invoiceHash, parentId ? ANON_COMMENT_FEE : ANON_POST_FEE)
|
const invoice = await checkInvoice(models, invoiceHash, invoiceHmac, parentId ? ANON_COMMENT_FEE : ANON_POST_FEE)
|
||||||
author = invoice.user
|
author = invoice.user
|
||||||
trx.push(models.invoice.delete({ where: { hash: invoiceHash } }))
|
trx.push(models.invoice.delete({ where: { hash: invoiceHash } }))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { createInvoice, decodePaymentRequest, payViaPaymentRequest } from 'ln-service'
|
import { createInvoice, decodePaymentRequest, payViaPaymentRequest } from 'ln-service'
|
||||||
import { GraphQLError } from 'graphql'
|
import { GraphQLError } from 'graphql'
|
||||||
|
import crypto from 'crypto'
|
||||||
import serialize from './serial'
|
import serialize from './serial'
|
||||||
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
|
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
|
||||||
import lnpr from 'bolt11'
|
import lnpr from 'bolt11'
|
||||||
|
@ -40,6 +41,11 @@ export async function getInvoice (parent, { id }, { me, models }) {
|
||||||
return inv
|
return inv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createHmac (hash) {
|
||||||
|
const key = Buffer.from(process.env.INVOICE_HMAC_KEY, 'hex')
|
||||||
|
return crypto.createHmac('sha256', key).update(Buffer.from(hash, 'hex')).digest('hex')
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
invoice: getInvoice,
|
invoice: getInvoice,
|
||||||
|
@ -220,7 +226,12 @@ export default {
|
||||||
models.$queryRaw`SELECT * FROM create_invoice(${invoice.id}, ${invoice.request},
|
models.$queryRaw`SELECT * FROM create_invoice(${invoice.id}, ${invoice.request},
|
||||||
${expiresAt}::timestamp, ${amount * 1000}, ${user.id}::INTEGER, ${description})`)
|
${expiresAt}::timestamp, ${amount * 1000}, ${user.id}::INTEGER, ${description})`)
|
||||||
|
|
||||||
return inv
|
// the HMAC is only returned during invoice creation
|
||||||
|
// this makes sure that only the person who created this invoice
|
||||||
|
// has access to the HMAC
|
||||||
|
const hmac = createHmac(inv.hash)
|
||||||
|
|
||||||
|
return { ...inv, hmac }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -26,16 +26,16 @@ export default gql`
|
||||||
bookmarkItem(id: ID): Item
|
bookmarkItem(id: ID): Item
|
||||||
subscribeItem(id: ID): Item
|
subscribeItem(id: ID): Item
|
||||||
deleteItem(id: ID): Item
|
deleteItem(id: ID): Item
|
||||||
upsertLink(id: ID, sub: String, title: String!, url: String!, boost: Int, forward: String, invoiceHash: String): Item!
|
upsertLink(id: ID, sub: String, title: String!, url: String!, boost: Int, forward: String, invoiceHash: String, invoiceHmac: String): Item!
|
||||||
upsertDiscussion(id: ID, sub: String, title: String!, text: String, boost: Int, forward: String, invoiceHash: String): Item!
|
upsertDiscussion(id: ID, sub: String, title: String!, text: String, boost: Int, forward: String, invoiceHash: String, invoiceHmac: String): Item!
|
||||||
upsertBounty(id: ID, sub: String, title: String!, text: String, bounty: Int!, boost: Int, forward: String): Item!
|
upsertBounty(id: ID, sub: String, title: String!, text: String, bounty: Int!, boost: Int, forward: String): Item!
|
||||||
upsertJob(id: ID, sub: String!, title: String!, company: String!, location: String, remote: Boolean,
|
upsertJob(id: ID, sub: String!, title: String!, company: String!, location: String, remote: Boolean,
|
||||||
text: String!, url: String!, maxBid: Int!, status: String, logo: Int): Item!
|
text: String!, url: String!, maxBid: Int!, status: String, logo: Int): Item!
|
||||||
upsertPoll(id: ID, sub: String, title: String!, text: String, options: [String!]!, boost: Int, forward: String, invoiceHash: String): Item!
|
upsertPoll(id: ID, sub: String, title: String!, text: String, options: [String!]!, boost: Int, forward: String, invoiceHash: String, invoiceHmac: String): Item!
|
||||||
createComment(text: String!, parentId: ID!, invoiceHash: String): Item!
|
createComment(text: String!, parentId: ID!, invoiceHash: String, invoiceHmac: String): Item!
|
||||||
updateComment(id: ID!, text: String!): Item!
|
updateComment(id: ID!, text: String!): Item!
|
||||||
dontLikeThis(id: ID!): Boolean!
|
dontLikeThis(id: ID!): Boolean!
|
||||||
act(id: ID!, sats: Int, invoiceHash: String): ItemActResult!
|
act(id: ID!, sats: Int, invoiceHash: String, invoiceHmac: String): ItemActResult!
|
||||||
pollVote(id: ID!): ID!
|
pollVote(id: ID!): ID!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ export default gql`
|
||||||
confirmedAt: Date
|
confirmedAt: Date
|
||||||
satsReceived: Int
|
satsReceived: Int
|
||||||
nostr: JSONObject
|
nostr: JSONObject
|
||||||
|
hmac: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type Withdrawl {
|
type Withdrawl {
|
||||||
|
|
|
@ -53,7 +53,7 @@ export function BountyForm ({
|
||||||
|
|
||||||
const submitUpsertBounty = useCallback(
|
const submitUpsertBounty = useCallback(
|
||||||
// we ignore the invoice since only stackers can post bounties
|
// we ignore the invoice since only stackers can post bounties
|
||||||
async (_, boost, bounty, values, __) => {
|
async (_, boost, bounty, values, ...__) => {
|
||||||
const { error } = await upsertBounty({
|
const { error } = await upsertBounty({
|
||||||
variables: {
|
variables: {
|
||||||
sub: item?.subName || sub?.name,
|
sub: item?.subName || sub?.name,
|
||||||
|
|
|
@ -29,17 +29,17 @@ export function DiscussionForm ({
|
||||||
// const me = useMe()
|
// const me = useMe()
|
||||||
const [upsertDiscussion] = useMutation(
|
const [upsertDiscussion] = useMutation(
|
||||||
gql`
|
gql`
|
||||||
mutation upsertDiscussion($sub: String, $id: ID, $title: String!, $text: String, $boost: Int, $forward: String, $invoiceHash: String) {
|
mutation upsertDiscussion($sub: String, $id: ID, $title: String!, $text: String, $boost: Int, $forward: String, $invoiceHash: String, $invoiceHmac: String) {
|
||||||
upsertDiscussion(sub: $sub, id: $id, title: $title, text: $text, boost: $boost, forward: $forward, invoiceHash: $invoiceHash) {
|
upsertDiscussion(sub: $sub, id: $id, title: $title, text: $text, boost: $boost, forward: $forward, invoiceHash: $invoiceHash, invoiceHmac: $invoiceHmac) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
|
|
||||||
const submitUpsertDiscussion = useCallback(
|
const submitUpsertDiscussion = useCallback(
|
||||||
async (_, boost, values, invoiceHash) => {
|
async (_, boost, values, invoiceHash, invoiceHmac) => {
|
||||||
const { error } = await upsertDiscussion({
|
const { error } = await upsertDiscussion({
|
||||||
variables: { sub: item?.subName || sub?.name, id: item?.id, boost: boost ? Number(boost) : undefined, ...values, invoiceHash }
|
variables: { sub: item?.subName || sub?.name, id: item?.id, boost: boost ? Number(boost) : undefined, ...values, invoiceHash, invoiceHmac }
|
||||||
})
|
})
|
||||||
if (error) {
|
if (error) {
|
||||||
throw new Error({ message: error.toString() })
|
throw new Error({ message: error.toString() })
|
||||||
|
|
|
@ -60,7 +60,7 @@ export function Invoice ({ invoice, onConfirmation, successVerb }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Contacts = ({ invoiceHash }) => {
|
const Contacts = ({ invoiceHash, invoiceHmac }) => {
|
||||||
const subject = `Support request for payment hash: ${invoiceHash}`
|
const subject = `Support request for payment hash: ${invoiceHash}`
|
||||||
const body = 'Hi, I successfully paid for <insert action> but the action did not work.'
|
const body = 'Hi, I successfully paid for <insert action> but the action did not work.'
|
||||||
return (
|
return (
|
||||||
|
@ -69,6 +69,10 @@ const Contacts = ({ invoiceHash }) => {
|
||||||
<div className='w-100'>
|
<div className='w-100'>
|
||||||
<CopyInput type='text' placeholder={invoiceHash} readOnly noForm />
|
<CopyInput type='text' placeholder={invoiceHash} readOnly noForm />
|
||||||
</div>
|
</div>
|
||||||
|
<span>Payment HMAC</span>
|
||||||
|
<div className='w-100'>
|
||||||
|
<CopyInput type='text' placeholder={invoiceHmac} readOnly noForm />
|
||||||
|
</div>
|
||||||
<div className='d-flex flex-row justify-content-center'>
|
<div className='d-flex flex-row justify-content-center'>
|
||||||
<a
|
<a
|
||||||
href={`mailto:kk@stacker.news?subject=${subject}&body=${body}`} className='nav-link p-0 d-inline-flex'
|
href={`mailto:kk@stacker.news?subject=${subject}&body=${body}`} className='nav-link p-0 d-inline-flex'
|
||||||
|
@ -102,7 +106,7 @@ const Contacts = ({ invoiceHash }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ActionInvoice = ({ id, hash, errorCount, repeat, ...props }) => {
|
const ActionInvoice = ({ id, hash, hmac, errorCount, repeat, ...props }) => {
|
||||||
const { data, loading, error } = useQuery(INVOICE, {
|
const { data, loading, error } = useQuery(INVOICE, {
|
||||||
pollInterval: 1000,
|
pollInterval: 1000,
|
||||||
variables: { id }
|
variables: { id }
|
||||||
|
@ -130,7 +134,7 @@ const ActionInvoice = ({ id, hash, errorCount, repeat, ...props }) => {
|
||||||
<InvoiceStatus variant='failed' status={errorStatus} />
|
<InvoiceStatus variant='failed' status={errorStatus} />
|
||||||
{errorCount === 1
|
{errorCount === 1
|
||||||
? <div className='d-flex flex-row mt-3 justify-content-center'><Button variant='info' onClick={repeat}>Retry</Button></div>
|
? <div className='d-flex flex-row mt-3 justify-content-center'><Button variant='info' onClick={repeat}>Retry</Button></div>
|
||||||
: <Contacts invoiceHash={hash} />}
|
: <Contacts invoiceHash={hash} invoiceHmac={hmac} />}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
: null}
|
: null}
|
||||||
|
@ -149,6 +153,7 @@ export const useInvoiceable = (fn, options = defaultOptions) => {
|
||||||
createInvoice(amount: $amount) {
|
createInvoice(amount: $amount) {
|
||||||
id
|
id
|
||||||
hash
|
hash
|
||||||
|
hmac
|
||||||
}
|
}
|
||||||
}`)
|
}`)
|
||||||
const showModal = useShowModal()
|
const showModal = useShowModal()
|
||||||
|
@ -157,11 +162,11 @@ export const useInvoiceable = (fn, options = defaultOptions) => {
|
||||||
// fix for bug where `showModal` runs the code for two modals and thus executes `onConfirmation` twice
|
// fix for bug where `showModal` runs the code for two modals and thus executes `onConfirmation` twice
|
||||||
let errorCount = 0
|
let errorCount = 0
|
||||||
const onConfirmation = useCallback(
|
const onConfirmation = useCallback(
|
||||||
onClose => {
|
(onClose, hmac) => {
|
||||||
return async ({ id, satsReceived, hash }) => {
|
return async ({ id, satsReceived, hash }) => {
|
||||||
await sleep(2000)
|
await sleep(2000)
|
||||||
const repeat = () =>
|
const repeat = () =>
|
||||||
fn(satsReceived, ...fnArgs, hash)
|
fn(satsReceived, ...fnArgs, hash, hmac)
|
||||||
.then(onClose)
|
.then(onClose)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
@ -171,7 +176,8 @@ export const useInvoiceable = (fn, options = defaultOptions) => {
|
||||||
<ActionInvoice
|
<ActionInvoice
|
||||||
id={id}
|
id={id}
|
||||||
hash={hash}
|
hash={hash}
|
||||||
onConfirmation={onConfirmation(onClose)}
|
hmac={hmac}
|
||||||
|
onConfirmation={onConfirmation(onClose, hmac)}
|
||||||
successVerb='received'
|
successVerb='received'
|
||||||
errorCount={errorCount}
|
errorCount={errorCount}
|
||||||
repeat={repeat}
|
repeat={repeat}
|
||||||
|
@ -191,7 +197,8 @@ export const useInvoiceable = (fn, options = defaultOptions) => {
|
||||||
<ActionInvoice
|
<ActionInvoice
|
||||||
id={invoice.id}
|
id={invoice.id}
|
||||||
hash={invoice.hash}
|
hash={invoice.hash}
|
||||||
onConfirmation={onConfirmation(onClose)}
|
hmac={invoice.hmac}
|
||||||
|
onConfirmation={onConfirmation(onClose, invoice.hmac)}
|
||||||
successVerb='received'
|
successVerb='received'
|
||||||
/>
|
/>
|
||||||
), { keepOpen: true }
|
), { keepOpen: true }
|
||||||
|
@ -213,7 +220,7 @@ export const useInvoiceable = (fn, options = defaultOptions) => {
|
||||||
<FundError
|
<FundError
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
amount={amount}
|
amount={amount}
|
||||||
onPayment={async (_, invoiceHash) => { await fn(amount, ...args, invoiceHash) }}
|
onPayment={async (_, invoiceHash, invoiceHmac) => { await fn(amount, ...args, invoiceHash, invoiceHmac) }}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -47,7 +47,7 @@ export default function ItemAct ({ onClose, itemId, act, strike }) {
|
||||||
}, [onClose, itemId])
|
}, [onClose, itemId])
|
||||||
|
|
||||||
const submitAct = useCallback(
|
const submitAct = useCallback(
|
||||||
async (amount, invoiceHash) => {
|
async (amount, invoiceHash, invoiceHmac) => {
|
||||||
if (!me) {
|
if (!me) {
|
||||||
const storageKey = `TIP-item:${itemId}`
|
const storageKey = `TIP-item:${itemId}`
|
||||||
const existingAmount = Number(window.localStorage.getItem(storageKey) || '0')
|
const existingAmount = Number(window.localStorage.getItem(storageKey) || '0')
|
||||||
|
@ -57,7 +57,8 @@ export default function ItemAct ({ onClose, itemId, act, strike }) {
|
||||||
variables: {
|
variables: {
|
||||||
id: itemId,
|
id: itemId,
|
||||||
sats: Number(amount),
|
sats: Number(amount),
|
||||||
invoiceHash
|
invoiceHash,
|
||||||
|
invoiceHmac
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
await strike()
|
await strike()
|
||||||
|
|
|
@ -53,7 +53,7 @@ export default function JobForm ({ item, sub }) {
|
||||||
|
|
||||||
const submitUpsertJob = useCallback(
|
const submitUpsertJob = useCallback(
|
||||||
// we ignore the invoice since only stackers can post jobs
|
// we ignore the invoice since only stackers can post jobs
|
||||||
async (_, maxBid, stop, start, values, __) => {
|
async (_, maxBid, stop, start, values, ...__) => {
|
||||||
let status
|
let status
|
||||||
if (start) {
|
if (start) {
|
||||||
status = 'ACTIVE'
|
status = 'ACTIVE'
|
||||||
|
|
|
@ -67,17 +67,17 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
|
||||||
|
|
||||||
const [upsertLink] = useMutation(
|
const [upsertLink] = useMutation(
|
||||||
gql`
|
gql`
|
||||||
mutation upsertLink($sub: String, $id: ID, $title: String!, $url: String!, $boost: Int, $forward: String, $invoiceHash: String) {
|
mutation upsertLink($sub: String, $id: ID, $title: String!, $url: String!, $boost: Int, $forward: String, $invoiceHash: String, $invoiceHmac: String) {
|
||||||
upsertLink(sub: $sub, id: $id, title: $title, url: $url, boost: $boost, forward: $forward, invoiceHash: $invoiceHash) {
|
upsertLink(sub: $sub, id: $id, title: $title, url: $url, boost: $boost, forward: $forward, invoiceHash: $invoiceHash, invoiceHmac: $invoiceHmac) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
|
|
||||||
const submitUpsertLink = useCallback(
|
const submitUpsertLink = useCallback(
|
||||||
async (_, boost, title, values, invoiceHash) => {
|
async (_, boost, title, values, invoiceHash, invoiceHmac) => {
|
||||||
const { error } = await upsertLink({
|
const { error } = await upsertLink({
|
||||||
variables: { sub: item?.subName || sub?.name, id: item?.id, boost: boost ? Number(boost) : undefined, title: title.trim(), invoiceHash, ...values }
|
variables: { sub: item?.subName || sub?.name, id: item?.id, boost: boost ? Number(boost) : undefined, title: title.trim(), invoiceHash, invoiceHmac, ...values }
|
||||||
})
|
})
|
||||||
if (error) {
|
if (error) {
|
||||||
throw new Error({ message: error.toString() })
|
throw new Error({ message: error.toString() })
|
||||||
|
|
|
@ -21,16 +21,16 @@ export function PollForm ({ item, sub, editThreshold, children }) {
|
||||||
const [upsertPoll] = useMutation(
|
const [upsertPoll] = useMutation(
|
||||||
gql`
|
gql`
|
||||||
mutation upsertPoll($sub: String, $id: ID, $title: String!, $text: String,
|
mutation upsertPoll($sub: String, $id: ID, $title: String!, $text: String,
|
||||||
$options: [String!]!, $boost: Int, $forward: String, $invoiceHash: String) {
|
$options: [String!]!, $boost: Int, $forward: String, $invoiceHash: String, $invoiceHmac: String) {
|
||||||
upsertPoll(sub: $sub, id: $id, title: $title, text: $text,
|
upsertPoll(sub: $sub, id: $id, title: $title, text: $text,
|
||||||
options: $options, boost: $boost, forward: $forward, invoiceHash: $invoiceHash) {
|
options: $options, boost: $boost, forward: $forward, invoiceHash: $invoiceHash, invoiceHmac: $invoiceHmac) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
|
|
||||||
const submitUpsertPoll = useCallback(
|
const submitUpsertPoll = useCallback(
|
||||||
async (_, boost, title, options, values, invoiceHash) => {
|
async (_, boost, title, options, values, invoiceHash, invoiceHmac) => {
|
||||||
const optionsFiltered = options.slice(initialOptions?.length).filter(word => word.trim().length > 0)
|
const optionsFiltered = options.slice(initialOptions?.length).filter(word => word.trim().length > 0)
|
||||||
const { error } = await upsertPoll({
|
const { error } = await upsertPoll({
|
||||||
variables: {
|
variables: {
|
||||||
|
@ -40,7 +40,8 @@ export function PollForm ({ item, sub, editThreshold, children }) {
|
||||||
title: title.trim(),
|
title: title.trim(),
|
||||||
options: optionsFiltered,
|
options: optionsFiltered,
|
||||||
...values,
|
...values,
|
||||||
invoiceHash
|
invoiceHash,
|
||||||
|
invoiceHmac
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|
|
@ -46,8 +46,8 @@ export default function Reply ({ item, onSuccess, replyOpen, children, placehold
|
||||||
const [createComment] = useMutation(
|
const [createComment] = useMutation(
|
||||||
gql`
|
gql`
|
||||||
${COMMENTS}
|
${COMMENTS}
|
||||||
mutation createComment($text: String!, $parentId: ID!, $invoiceHash: String) {
|
mutation createComment($text: String!, $parentId: ID!, $invoiceHash: String, $invoiceHmac: String) {
|
||||||
createComment(text: $text, parentId: $parentId, invoiceHash: $invoiceHash) {
|
createComment(text: $text, parentId: $parentId, invoiceHash: $invoiceHash, invoiceHmac: $invoiceHmac) {
|
||||||
...CommentFields
|
...CommentFields
|
||||||
comments {
|
comments {
|
||||||
...CommentsRecursive
|
...CommentsRecursive
|
||||||
|
@ -92,8 +92,8 @@ export default function Reply ({ item, onSuccess, replyOpen, children, placehold
|
||||||
)
|
)
|
||||||
|
|
||||||
const submitComment = useCallback(
|
const submitComment = useCallback(
|
||||||
async (_, values, parentId, resetForm, invoiceHash) => {
|
async (_, values, parentId, resetForm, invoiceHash, invoiceHmac) => {
|
||||||
const { error } = await createComment({ variables: { ...values, parentId, invoiceHash } })
|
const { error } = await createComment({ variables: { ...values, parentId, invoiceHash, invoiceHmac } })
|
||||||
if (error) {
|
if (error) {
|
||||||
throw new Error({ message: error.toString() })
|
throw new Error({ message: error.toString() })
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,8 +110,8 @@ export default function UpVote ({ item, className, pendingSats, setPendingSats }
|
||||||
|
|
||||||
const [act] = useMutation(
|
const [act] = useMutation(
|
||||||
gql`
|
gql`
|
||||||
mutation act($id: ID!, $sats: Int!, $invoiceHash: String) {
|
mutation act($id: ID!, $sats: Int!, $invoiceHash: String, $invoiceHmac: String) {
|
||||||
act(id: $id, sats: $sats, invoiceHash: $invoiceHash) {
|
act(id: $id, sats: $sats, invoiceHash: $invoiceHash, invoiceHmac: $invoiceHmac) {
|
||||||
sats
|
sats
|
||||||
}
|
}
|
||||||
}`, {
|
}`, {
|
||||||
|
|
Loading…
Reference in New Issue