diff --git a/apps/boltcard/TODO.txt b/apps/boltcard/TODO.txt index a3d2c79c2dc..df0c3c92990 100644 --- a/apps/boltcard/TODO.txt +++ b/apps/boltcard/TODO.txt @@ -4,3 +4,5 @@ - Tilt setup - verify writing card do the callback/creation of Card entity - open telemetry + +- add lnurl / lightning address / invoice diff --git a/apps/boltcard/app/api/card/id/[id]/route.ts b/apps/boltcard/app/api/card/id/[id]/route.ts index 427fba6bf85..0a3e249c3ad 100644 --- a/apps/boltcard/app/api/card/id/[id]/route.ts +++ b/apps/boltcard/app/api/card/id/[id]/route.ts @@ -1,10 +1,10 @@ import { NextRequest, NextResponse } from "next/server" -import { fetchByCardId } from "@/services/db/card" +import { fetchPublicByCardId } from "@/services/db/card" export async function GET(req: NextRequest, { params }: { params: { id: string } }) { const id = params.id - const card = await fetchByCardId(id) + const card = await fetchPublicByCardId(id) if (!card) { return NextResponse.json( { status: "ERROR", reason: "card not found" }, diff --git a/apps/boltcard/app/api/card/id/[id]/transactions/route.ts b/apps/boltcard/app/api/card/id/[id]/transactions/route.ts index f91fb31c370..f530e7d582c 100644 --- a/apps/boltcard/app/api/card/id/[id]/transactions/route.ts +++ b/apps/boltcard/app/api/card/id/[id]/transactions/route.ts @@ -2,9 +2,9 @@ import { NextRequest, NextResponse } from "next/server" import { gql } from "@apollo/client" -import { fetchByCardId } from "@/services/db/card" import { apollo } from "@/services/core" import { TransactionsDocument, TransactionsQuery } from "@/services/core/generated" +import { fetchByCardId } from "@/services/db/card" gql` query transactions($first: Int, $after: String) { diff --git a/apps/boltcard/app/api/card/uid/[uid]/route.ts b/apps/boltcard/app/api/card/uid/[uid]/route.ts index 629ab550513..f66bfc297e5 100644 --- a/apps/boltcard/app/api/card/uid/[uid]/route.ts +++ b/apps/boltcard/app/api/card/uid/[uid]/route.ts @@ -1,10 +1,10 @@ import { NextRequest, NextResponse } from "next/server" -import { fetchByUid } from "@/services/db/card" +import { fetchPublicByCardUid } from "@/services/db/card" export async function GET(req: NextRequest, { params }: { params: { uid: string } }) { const uid = params.uid - const card = await fetchByUid(uid) + const card = await fetchPublicByCardUid(uid) if (!card) { return NextResponse.json( { status: "ERROR", reason: "card not found" }, diff --git a/apps/boltcard/app/api/createboltcard/route.ts b/apps/boltcard/app/api/createboltcard/route.ts index 58eaa32b89d..42952a571dc 100644 --- a/apps/boltcard/app/api/createboltcard/route.ts +++ b/apps/boltcard/app/api/createboltcard/route.ts @@ -3,7 +3,7 @@ import { randomBytes } from "crypto" import { NextRequest, NextResponse } from "next/server" import { createCardInit } from "@/services/db/card-init" -import { serverUrl } from "@/services/config" +import { serverApi } from "@/services/config" const randomHex = (): string => randomBytes(16).toString("hex") @@ -45,7 +45,7 @@ export async function GET(req: NextRequest) { ) } - const url = `${serverUrl}/new?a=${oneTimeCode}` + const url = `${serverApi}/new?a=${oneTimeCode}` return NextResponse.json({ status: "OK", url, diff --git a/apps/boltcard/app/api/ln/route.ts b/apps/boltcard/app/api/ln/route.ts index 51888955bf0..881fe278b2b 100644 --- a/apps/boltcard/app/api/ln/route.ts +++ b/apps/boltcard/app/api/ln/route.ts @@ -4,7 +4,7 @@ import { NextRequest, NextResponse } from "next/server" import { ApolloQueryResult, gql } from "@apollo/client" -import { aesDecryptKey, serverUrl } from "@/services/config" +import { aesDecryptKey, serverApi } from "@/services/config" import { aesDecrypt, checkSignature } from "@/services/crypto/aes" import { decodePToUidCtr } from "@/services/crypto/decoder" import { createCard, fetchByUid } from "@/services/db/card" @@ -98,6 +98,96 @@ const maybeSetupCard = async ({ return null } +const setupCard = async ({ + cardInit, + uid, + ctr, +}: { + cardInit: CardInitInput + uid: string + ctr: number +}): Promise => { + const { k0AuthKey, k2CmacKey, k3, k4, token } = cardInit + + const client = apollo(token).getClient() + + let data: ApolloQueryResult + try { + data = await client.query({ + query: GetUsdWalletIdDocument, + }) + } catch (error) { + console.error(error) + return NextResponse.json( + { status: "ERROR", reason: "issue fetching walletId" }, + { status: 400 }, + ) + } + + const accountId = data.data?.me?.defaultAccount?.id + if (!accountId) { + return NextResponse.json( + { status: "ERROR", reason: "no accountId found" }, + { status: 400 }, + ) + } + + const wallets = data.data?.me?.defaultAccount.wallets + + if (!wallets) { + return NextResponse.json( + { status: "ERROR", reason: "no wallets found" }, + { status: 400 }, + ) + } + + const usdWallet = wallets.find((wallet) => wallet.walletCurrency === "USD") + const walletId = usdWallet?.id + + console.log({ usdWallet, wallets }) + + if (!walletId) { + return NextResponse.json( + { status: "ERROR", reason: "no usd wallet found" }, + { status: 400 }, + ) + } + + const dataOnchain = await client.mutate({ + mutation: OnChainAddressCurrentDocument, + variables: { input: { walletId } }, + }) + const onchainAddress = dataOnchain.data?.onChainAddressCurrent?.address + if (!onchainAddress) { + console.log(dataOnchain.data?.onChainAddressCurrent, "dataOnchain") + return NextResponse.json( + { status: "ERROR", reason: `onchain address not found` }, + { status: 400 }, + ) + } + + const id = generateReadableCode(12) + console.log({ id, onchainAddress }, "new card id") + + await markCardInitAsUsed(k2CmacKey) + + const card = await createCard({ + id, + uid, + k0AuthKey, + k2CmacKey, + k3, + k4, + ctr, + token, + accountId, + onchainAddress, + walletId, + }) + + return card +} + gql` query getUsdWalletId { me { @@ -159,86 +249,14 @@ export async function GET(req: NextRequest) { let card = await fetchByUid(uid) if (!card) { - const result = await maybeSetupCard({ uidRaw, ctrRawInverseBytes, ba_c }) - - if (result) { - const { k0AuthKey, k2CmacKey, k3, k4, token } = result - - const client = apollo(token).getClient() - - let data: ApolloQueryResult - try { - data = await client.query({ - query: GetUsdWalletIdDocument, - }) - } catch (error) { - console.error(error) - return NextResponse.json( - { status: "ERROR", reason: "issue fetching walletId" }, - { status: 400 }, - ) - } - - const accountId = data.data?.me?.defaultAccount?.id - if (!accountId) { - return NextResponse.json( - { status: "ERROR", reason: "no accountId found" }, - { status: 400 }, - ) - } + const cardInit = await maybeSetupCard({ uidRaw, ctrRawInverseBytes, ba_c }) - const wallets = data.data?.me?.defaultAccount.wallets + if (cardInit) { + card = await setupCard({ cardInit, uid, ctr }) - if (!wallets) { - return NextResponse.json( - { status: "ERROR", reason: "no wallets found" }, - { status: 400 }, - ) + if (card instanceof NextResponse) { + return card } - - const usdWallet = wallets.find((wallet) => wallet.walletCurrency === "USD") - const walletId = usdWallet?.id - - console.log({ usdWallet, wallets }) - - if (!walletId) { - return NextResponse.json( - { status: "ERROR", reason: "no usd wallet found" }, - { status: 400 }, - ) - } - - const dataOnchain = await client.mutate({ - mutation: OnChainAddressCurrentDocument, - variables: { input: { walletId } }, - }) - const onchainAddress = dataOnchain.data?.onChainAddressCurrent?.address - if (!onchainAddress) { - console.log(dataOnchain.data?.onChainAddressCurrent, "dataOnchain") - return NextResponse.json( - { status: "ERROR", reason: `onchain address not found` }, - { status: 400 }, - ) - } - - const id = generateReadableCode(12) - console.log({ id, onchainAddress }, "new card id") - - await markCardInitAsUsed(k2CmacKey) - - card = await createCard({ - id, - uid, - k0AuthKey, - k2CmacKey, - k3, - k4, - ctr, - token, - accountId, - onchainAddress, - walletId, - }) } else { return NextResponse.json( { status: "ERROR", reason: "card not found" }, @@ -270,7 +288,7 @@ export async function GET(req: NextRequest) { return NextResponse.json({ tag: "withdrawRequest", - callback: serverUrl + "/callback", + callback: serverApi + "/callback", k1, defaultDescription: "payment for a blink card", minWithdrawable: 1000, diff --git a/apps/boltcard/app/api/new/route.ts b/apps/boltcard/app/api/new/route.ts index ce619b697e8..d5e02b2ae47 100644 --- a/apps/boltcard/app/api/new/route.ts +++ b/apps/boltcard/app/api/new/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from "next/server" -import { aesDecryptKey, serverUrl } from "@/services/config" +import { aesDecryptKey, serverApi } from "@/services/config" import { fetchByOneTimeCode } from "@/services/db/card-init" interface NewCardResponse { @@ -43,7 +43,7 @@ export async function GET(req: NextRequest) { ) } - const lnurlwBase = `${serverUrl}/ln` + const lnurlwBase = `${serverApi}/ln` .replace("http://", "lnurlw://") .replace("https://", "lnurlw://") diff --git a/apps/boltcard/app/card/[id]/page.tsx b/apps/boltcard/app/card/[id]/page.tsx index 403f3bbc489..3e10d3611a1 100644 --- a/apps/boltcard/app/card/[id]/page.tsx +++ b/apps/boltcard/app/card/[id]/page.tsx @@ -1,9 +1,71 @@ -export default function Home({ params }: { params: { id: string } }) { +import { serverApi } from "@/services/config" + +export default async function Home({ params }: { params: { id: string } }) { const { id } = params + const cardApi = `${serverApi}/card/id/${id}` + const cardResult = await fetch(cardApi, { cache: "no-store" }) + const cardInfo = await cardResult.json() + + const transactionsApi = `${serverApi}/card/id/${id}/transactions` + const transactionsResult = await fetch(transactionsApi) + const transactionInfo = await transactionsResult.json() + return ( -
-

cardId: {id}

+
+

boltcard

+ +
+

Card Information:

+
    +
  • + ID: {cardInfo.id} +
  • +
  • + UID: {cardInfo.uid} +
  • +
  • + Onchain Address: {cardInfo.onchainAddress} +
  • +
  • + Enabled: {cardInfo.enabled ? "Yes" : "No"} +
  • +
+
+ +
+

Transactions:

+
    + {transactionInfo.map((tx, index) => ( +
  • + Transaction {index + 1}: +
      +
    • + ID: {tx.id} +
    • +
    • + Status: {tx.status} +
    • +
    • + Direction: {tx.direction} +
    • +
    • + Memo: {tx.memo} +
    • +
    • + Amount: {tx.settlementDisplayAmount}{" "} + {tx.settlementDisplayCurrency} +
    • +
    • + Fee: {tx.settlementDisplayFee}{" "} + {tx.settlementDisplayCurrency} +
    • + {/* Add more fields as required */} +
    +
  • + ))} +
+
) } diff --git a/apps/boltcard/bun.lockb b/apps/boltcard/bun.lockb index 960489c7199..8a223069851 100755 Binary files a/apps/boltcard/bun.lockb and b/apps/boltcard/bun.lockb differ diff --git a/apps/boltcard/package.json b/apps/boltcard/package.json index 3eccde4c6c8..f921d43f195 100644 --- a/apps/boltcard/package.json +++ b/apps/boltcard/package.json @@ -22,7 +22,7 @@ "eslint-config-next": "13.4.19", "graphql": "^16.8.0", "knex": "^2.5.1", - "next": "13.4.19", + "next": "latest", "node-aes-cmac": "^0.1.1", "pg": "^8.11.3", "postcss": "8.4.29", diff --git a/apps/boltcard/services/config.ts b/apps/boltcard/services/config.ts index d2bb3f76fb2..a2b9a7b8404 100644 --- a/apps/boltcard/services/config.ts +++ b/apps/boltcard/services/config.ts @@ -1,4 +1,4 @@ -export const serverUrl = process.env.SERVER_URL ?? "http://localhost:3000/api" +export const serverApi = process.env.SERVER_URL ?? "http://localhost:3000/api" export const coreUrl = process.env.CORE_URL ?? "http://localhost:4002/graphql" diff --git a/apps/boltcard/services/db/card.ts b/apps/boltcard/services/db/card.ts index fbfafc3b442..f8854a82000 100644 --- a/apps/boltcard/services/db/card.ts +++ b/apps/boltcard/services/db/card.ts @@ -10,6 +10,22 @@ export async function fetchByCardId(cardId: string) { return result } +export async function fetchPublicByCardUid(uid: string) { + const result = await knex("Card") + .where("uid", uid) + .select("id", "uid", "onchainAddress", "enabled") + .first() + return result +} + +export async function fetchPublicByCardId(cardId: string) { + const result = await knex("Card") + .where("id", cardId) + .select("id", "uid", "onchainAddress", "enabled") + .first() + return result +} + interface CardInput { id: string uid: string