Skip to content
This repository has been archived by the owner on Dec 7, 2023. It is now read-only.

Commit

Permalink
chore: splitting callback in another api endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicolas Burtey committed Aug 21, 2023
1 parent e289393 commit d456f09
Show file tree
Hide file tree
Showing 2 changed files with 230 additions and 80 deletions.
214 changes: 214 additions & 0 deletions app/lnurlp/[username]/callback/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import { NextResponse } from "next/server"
import { URL } from "url"

import crypto from "crypto"
import {
ApolloClient,
ApolloLink,
concat,
gql,
HttpLink,
InMemoryCache,
} from "@apollo/client"
import Redis from "ioredis"

import { GRAPHQL_URI_INTERNAL, NOSTR_PUBKEY } from "../../../../lib/config"
import {
AccountDefaultWalletDocument,
AccountDefaultWalletQuery,
LnInvoiceCreateOnBehalfOfRecipientDocument,
LnInvoiceCreateOnBehalfOfRecipientMutation,
} from "../../../../lib/graphql/generated"

const ipForwardingMiddleware = new ApolloLink((operation, forward) => {
operation.setContext(({ headers = {} }) => ({
headers: {
...headers,
"x-real-ip": operation.getContext()["x-real-ip"],
"x-forwarded-for": operation.getContext()["x-forwarded-for"],
},
}))

return forward(operation)
})

const client = new ApolloClient({
link: concat(
ipForwardingMiddleware,
new HttpLink({
uri: GRAPHQL_URI_INTERNAL,
}),
),
cache: new InMemoryCache(),
})

gql`
query accountDefaultWallet($username: Username!, $walletCurrency: WalletCurrency!) {
accountDefaultWallet(username: $username, walletCurrency: $walletCurrency) {
__typename
id
walletCurrency
}
}
mutation lnInvoiceCreateOnBehalfOfRecipient(
$walletId: WalletId!
$amount: SatAmount!
$descriptionHash: Hex32Bytes!
) {
mutationData: lnInvoiceCreateOnBehalfOfRecipient(
input: {
recipientWalletId: $walletId
amount: $amount
descriptionHash: $descriptionHash
}
) {
errors {
message
}
invoice {
paymentRequest
paymentHash
}
}
}
`

const nostrEnabled = !!NOSTR_PUBKEY

let redis: Redis | null = null

if (nostrEnabled) {
const connectionObj = {
sentinelPassword: process.env.REDIS_PASSWORD,
sentinels: [
{
host: `${process.env.REDIS_0_DNS}`,
port: 26379,
},
{
host: `${process.env.REDIS_1_DNS}`,
port: 26379,
},
{
host: `${process.env.REDIS_2_DNS}`,
port: 26379,
},
],
name: process.env.REDIS_MASTER_NAME ?? "mymaster",
password: process.env.REDIS_PASSWORD,
}

redis = new Redis(connectionObj)

redis.on("error", (err) => console.log({ err }, "Redis error"))
}

export async function GET(
request: Request,
{ params }: { params: { username: string } },
) {
console.log(NOSTR_PUBKEY)

const { searchParams, hostname } = new URL(request.url)

const username = params.username

// amount has to be in millisats for this to work
// this is part of the lnurl spec
const amount = searchParams.get("amount")
const nostr = searchParams.get("nostr")

if (!amount || !username) {
return NextResponse.json({
status: "ERROR",
reason: "Invalid request",
})
}

let walletId: string | null = null

try {
const { data } = await client.query<AccountDefaultWalletQuery>({
query: AccountDefaultWalletDocument,
variables: { username, walletCurrency: "BTC" },
context: {
"x-real-ip": request.headers.get("x-real-ip"),
"x-forwarded-for": request.headers.get("x-forwarded-for"),
},
})
walletId = data?.accountDefaultWallet?.id
} catch (err: unknown) {
console.log(err)
}

if (!walletId) {
return NextResponse.json({
status: "ERROR",
reason: `Couldn't find user '${username}'.`,
})
}

const metadata = JSON.stringify([
["text/plain", `Payment to ${username}`],
["text/identifier", `${username}@${hostname}`],
])

// lnurl generate invoice
try {
if (Array.isArray(amount) || Array.isArray(nostr)) {
throw new Error("Invalid request")
}

const amountSats = Math.round(parseInt(amount, 10) / 1000)
if ((amountSats * 1000).toString() !== amount) {
return NextResponse.json({
status: "ERROR",
reason: "Millisatoshi amount is not supported, please send a value in full sats.",
})
}

let descriptionHash: string

if (nostrEnabled && nostr) {
descriptionHash = crypto.createHash("sha256").update(nostr).digest("hex")
} else {
descriptionHash = crypto.createHash("sha256").update(metadata).digest("hex")
}

const result = await client.mutate<LnInvoiceCreateOnBehalfOfRecipientMutation>({
mutation: LnInvoiceCreateOnBehalfOfRecipientDocument,
variables: {
walletId,
amount: amountSats,
descriptionHash,
},
})

const errors = result.errors
const invoice = result.data?.mutationData?.invoice

if ((errors && errors.length) || !invoice) {
console.log("error getting invoice", errors)
return NextResponse.json({
status: "ERROR",
reason: `Failed to get invoice: ${errors ? errors[0].message : "unknown error"}`,
})
}

if (nostrEnabled && nostr && redis) {
redis.set(`nostrInvoice:${invoice.paymentHash}`, nostr, "EX", 1440)
}

return NextResponse.json({
pr: invoice.paymentRequest,
routes: [],
})
} catch (err: unknown) {
console.log("unexpected error getting invoice", err)
NextResponse.json({
status: "ERROR",
reason: err instanceof Error ? err.message : "unexpected error",
})
}
}
96 changes: 16 additions & 80 deletions app/lnurlp/[username]/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { NextResponse } from "next/server"
import { URL } from "url"

import crypto from "crypto"
import {
ApolloClient,
ApolloLink,
Expand All @@ -16,8 +15,6 @@ import { GRAPHQL_URI_INTERNAL, NOSTR_PUBKEY } from "../../../lib/config"
import {
AccountDefaultWalletDocument,
AccountDefaultWalletQuery,
LnInvoiceCreateOnBehalfOfRecipientDocument,
LnInvoiceCreateOnBehalfOfRecipientMutation,
} from "../../../lib/graphql/generated"

const ipForwardingMiddleware = new ApolloLink((operation, forward) => {
Expand Down Expand Up @@ -110,11 +107,9 @@ export async function GET(
) {
console.log(NOSTR_PUBKEY)

const { searchParams, hostname } = new URL(request.url)
const { hostname } = new URL(request.url)

const username = params.username
const amount = searchParams.get("amount")
const nostr = searchParams.get("nostr")

const accountUsername = username ? username.toString() : ""

Expand Down Expand Up @@ -146,78 +141,19 @@ export async function GET(
["text/identifier", `${accountUsername}@${hostname}`],
])

// lnurl options call
if (!amount) {
return NextResponse.json({
callback: request.url,
minSendable: 1000,
maxSendable: 100000000000,
metadata,
tag: "payRequest",
...(nostrEnabled
? {
allowsNostr: true,
nostrPubkey: NOSTR_PUBKEY,
}
: {}),
})
}

// lnurl generate invoice
try {
if (Array.isArray(amount) || Array.isArray(nostr)) {
throw new Error("Invalid request")
}

const amountSats = Math.round(parseInt(amount, 10) / 1000)
if ((amountSats * 1000).toString() !== amount) {
return NextResponse.json({
status: "ERROR",
reason: "Millisatoshi amount is not supported, please send a value in full sats.",
})
}

let descriptionHash: string

if (nostrEnabled && nostr) {
descriptionHash = crypto.createHash("sha256").update(nostr).digest("hex")
} else {
descriptionHash = crypto.createHash("sha256").update(metadata).digest("hex")
}

const result = await client.mutate<LnInvoiceCreateOnBehalfOfRecipientMutation>({
mutation: LnInvoiceCreateOnBehalfOfRecipientDocument,
variables: {
walletId,
amount: amountSats,
descriptionHash,
},
})

const errors = result.errors
const invoice = result.data?.mutationData?.invoice

if ((errors && errors.length) || !invoice) {
console.log("error getting invoice", errors)
return NextResponse.json({
status: "ERROR",
reason: `Failed to get invoice: ${errors ? errors[0].message : "unknown error"}`,
})
}

if (nostrEnabled && nostr && redis) {
redis.set(`nostrInvoice:${invoice.paymentHash}`, nostr, "EX", 1440)
}

return NextResponse.json({
pr: invoice.paymentRequest,
routes: [],
})
} catch (err: unknown) {
console.log("unexpected error getting invoice", err)
NextResponse.json({
status: "ERROR",
reason: err instanceof Error ? err.message : "unexpected error",
})
}
const callback = `${request.url}/callback`

return NextResponse.json({
callback,
minSendable: 1000,
maxSendable: 100000000000,
metadata,
tag: "payRequest",
...(nostrEnabled
? {
allowsNostr: true,
nostrPubkey: NOSTR_PUBKEY,
}
: {}),
})
}

0 comments on commit d456f09

Please sign in to comment.