diff --git a/apps/pay/components/ParsePOSPayment/Receive-Invoice.tsx b/apps/pay/components/ParsePOSPayment/Receive-Invoice.tsx
index 8ba2f355dd2..90492194564 100644
--- a/apps/pay/components/ParsePOSPayment/Receive-Invoice.tsx
+++ b/apps/pay/components/ParsePOSPayment/Receive-Invoice.tsx
@@ -2,7 +2,7 @@
// TODO: remove eslint-disable, the logic likely needs to be reworked
import copy from "copy-to-clipboard"
import { useRouter } from "next/router"
-import React, { useCallback } from "react"
+import React, { useCallback, useState } from "react"
import Image from "react-bootstrap/Image"
import OverlayTrigger from "react-bootstrap/OverlayTrigger"
import Tooltip from "react-bootstrap/Tooltip"
@@ -17,6 +17,8 @@ import { ACTION_TYPE } from "../../pages/_reducer"
import PaymentOutcome from "../PaymentOutcome"
import { Share } from "../Share"
+import { getParams } from "js-lnurl"
+
import { safeAmount } from "../../utils/utils"
import styles from "./parse-payment.module.css"
@@ -210,6 +212,99 @@ function ReceiveInvoice({ recipientWalletCurrency, walletId, state, dispatch }:
}
}
+ const [nfcMessage, setNfcMessage] = useState("")
+
+ const decodeNDEFRecord = (record) => {
+ // Ensure that the record's data is an instance of ArrayBuffer
+ if (record.data instanceof ArrayBuffer) {
+ const decoder = new TextDecoder(record.encoding || "utf-8")
+ return decoder.decode(record.data)
+ } else {
+ // If it's not an ArrayBuffer, it might be a DataView or another typed array.
+ // In that case, we can create a new Uint8Array from the buffer of the DataView.
+ const decoder = new TextDecoder(record.encoding || "utf-8")
+ return decoder.decode(new Uint8Array(record.data.buffer))
+ }
+ }
+
+ const handleNFCScan = () => {
+ if ("NDEFReader" in window) {
+ const ndef = new NDEFReader()
+ ndef
+ .scan()
+ .then(() => {
+ console.log("NFC scan started successfully.")
+
+ ndef.onreading = (event) => {
+ console.log("NFC tag read.")
+ const record = event.message.records[0]
+ const text = decodeNDEFRecord(record)
+
+ if (text.toLowerCase().includes("lnurl")) {
+ setNfcMessage(text)
+ // Handle your "lnurl" logic here...
+ }
+ }
+
+ ndef.onreadingerror = () => {
+ console.log("Cannot read data from the NFC tag. Try another one?")
+ }
+ })
+ .catch((error) => {
+ console.log(`Error! Scan failed to start: ${error}.`)
+ })
+ } else {
+ console.log("NFC is not supported")
+ }
+ }
+
+ React.useEffect(() => {
+ console.log("nfcMessage", nfcMessage)
+
+ setNfcMessage(
+ "lnurlw://boltcard.tiankii.app/v1/lnurl/b1pizbxx0ikdivim5tpt9csy9kezxi?p=960C0DDCE939D1295C301D6B1A65BE78&c=CDFC90874BCE5AF2",
+ )
+ }, [])
+
+ React.useEffect(() => {
+ ;(async () => {
+ if (nfcMessage) {
+ const lnurlParams = await getParams(nfcMessage)
+
+ console.log("lnurlParams", lnurlParams)
+
+ if (!("tag" in lnurlParams && lnurlParams.tag === "withdrawRequest")) {
+ console.error("not a lnurl withdraw tag")
+ return
+ }
+
+ if (!invoice?.paymentRequest) {
+ console.error("no invoice to redeem")
+ return
+ }
+
+ const { callback, k1 } = lnurlParams
+
+ const urlObject = new URL(callback)
+ const searchParams = urlObject.searchParams
+ searchParams.set("k1", k1)
+ searchParams.set("pr", invoice?.paymentRequest)
+
+ const url = urlObject.toString()
+
+ const result = await fetch(url)
+ if (result.ok) {
+ const lnurlResponse = await result.json()
+ if (lnurlResponse?.status?.toLowerCase() !== "ok") {
+ console.error(lnurlResponse, "error with redeeming")
+ }
+ } else {
+ console.error(result.text(), "error with submitting withdrawalRequest")
+ }
+ }
+ })()
+ }, [nfcMessage, invoice])
+
const copyInvoice = () => {
if (!invoice?.paymentRequest) {
return
@@ -242,6 +337,8 @@ function ReceiveInvoice({ recipientWalletCurrency, walletId, state, dispatch }:
)
}
+ console.log("invoice", invoice)
+
return (
{recipientWalletCurrency === "USD" && (
@@ -254,6 +351,9 @@ function ReceiveInvoice({ recipientWalletCurrency, walletId, state, dispatch }:
)}
+
+ {nfcMessage &&
LNURL: {nfcMessage}
}
+
{data ? (
<>
{
+ return text.toLowerCase().includes("lnurl")
+ }
+
+ const decodeNDEFRecord = (record) => {
+ // Ensure that the record's data is an instance of ArrayBuffer
+ if (record.data instanceof ArrayBuffer) {
+ const decoder = new TextDecoder(record.encoding || "utf-8")
+ return decoder.decode(record.data)
+ } else {
+ // If it's not an ArrayBuffer, it might be a DataView or another typed array.
+ // In that case, we can create a new Uint8Array from the buffer of the DataView.
+ const decoder = new TextDecoder(record.encoding || "utf-8")
+ return decoder.decode(new Uint8Array(record.data.buffer))
+ }
+ }
+
+ const handleNFCScan = () => {
+ if ("NDEFReader" in window) {
+ const ndef = new NDEFReader()
+ ndef
+ .scan()
+ .then(() => {
+ console.log("NFC scan started successfully.")
+
+ ndef.onreading = (event) => {
+ console.log("NFC tag read.")
+ const record = event.message.records[0]
+ const text = decodeNDEFRecord(record)
+
+ if (checkForLnurl(text)) {
+ setNfcMessage(text)
+ // Handle your "lnurl" logic here...
+ }
+ }
+
+ ndef.onreadingerror = () => {
+ console.log("Cannot read data from the NFC tag. Try another one?")
+ }
+ })
+ .catch((error) => {
+ console.log(`Error! Scan failed to start: ${error}.`)
+ })
+ } else {
+ console.log("NFC is not supported")
+ }
+ }
+
return (
+
+ {nfcMessage && LNURL: {nfcMessage}
}
+
@@ -68,8 +121,8 @@ function Home() {
{error
? "Unavailable"
: loading
- ? "Loading..."
- : data.globals.nodesIds[0]}
+ ? "Loading..."
+ : data.globals.nodesIds[0]}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d485357c008..53836cc645e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -604,6 +604,9 @@ importers:
ioredis:
specifier: ^5.3.1
version: 5.3.2
+ js-lnurl:
+ specifier: ^0.6.0
+ version: 0.6.0
lnurl-pay:
specifier: ^1.0.1
version: 1.0.1
@@ -13833,6 +13836,10 @@ packages:
engines: {node: '>=10.13.0'}
dev: false
+ /@types/aes-js@3.1.4:
+ resolution: {integrity: sha512-v3D66IptpUqh+pHKVNRxY8yvp2ESSZXe0rTzsGdzUhEwag7ljVfgCllkWv2YgiYXDhWFBrEywll4A5JToyTNFA==}
+ dev: false
+
/@types/aria-query@5.0.4:
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
dev: true
@@ -13870,6 +13877,10 @@ packages:
'@babel/types': 7.23.6
dev: true
+ /@types/base64-js@1.3.2:
+ resolution: {integrity: sha512-Q2Xn2/vQHRGLRXhQ5+BSLwhHkR3JVflxVKywH0Q6fVoAiUE8fFYL2pE5/l2ZiOiBDfA8qUqRnSxln4G/NFz1Sg==}
+ dev: false
+
/@types/basic-auth@1.1.7:
resolution: {integrity: sha512-bFR3Ld3Fty5ayg45sqr3RI4e/GTXyp2W8jzMmw3WOC8RuQ19TrpsZE4y3jcw9iGSZj5f9mH6e+2biPeFUDovww==}
dependencies:
@@ -17872,7 +17883,6 @@ packages:
node-fetch: 2.7.0
transitivePeerDependencies:
- encoding
- dev: true
/cross-inspect@1.0.0:
resolution: {integrity: sha512-4PFfn4b5ZN6FMNGSZlyb7wUhuN8wvj8t/VQHZdM4JsDcruGJ8L2kf9zao98QIrBPFCpdk27qst/AGTl7pL3ypQ==}
@@ -18189,7 +18199,6 @@ packages:
/decode-uri-component@0.2.2:
resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
engines: {node: '>=0.10'}
- dev: true
/decompress-response@3.3.0:
resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==}
@@ -20893,6 +20902,11 @@ packages:
dependencies:
to-regex-range: 5.0.1
+ /filter-obj@1.1.0:
+ resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
/finalhandler@1.1.2:
resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==}
engines: {node: '>= 0.8'}
@@ -24052,6 +24066,22 @@ packages:
nopt: 6.0.0
dev: true
+ /js-lnurl@0.6.0:
+ resolution: {integrity: sha512-U4hnInqlHVM9DyYMnOLk0IqlD293A7GVen8JBNWbXrq7C1IigQpTfoal+Fgz/eTZOsYtIEFEOyW9mKgFD/Oc0w==}
+ dependencies:
+ '@types/aes-js': 3.1.4
+ '@types/base64-js': 1.3.2
+ aes-js: 3.1.2
+ base64-js: 1.5.1
+ bech32: 1.1.4
+ buffer: 5.7.1
+ cross-fetch: 3.1.8
+ query-string: 6.14.1
+ safe-buffer: 5.2.1
+ transitivePeerDependencies:
+ - encoding
+ dev: false
+
/js-md5@0.7.3:
resolution: {integrity: sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ==}
dev: false
@@ -27553,6 +27583,16 @@ packages:
strict-uri-encode: 1.1.0
dev: true
+ /query-string@6.14.1:
+ resolution: {integrity: sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==}
+ engines: {node: '>=6'}
+ dependencies:
+ decode-uri-component: 0.2.2
+ filter-obj: 1.1.0
+ split-on-first: 1.1.0
+ strict-uri-encode: 2.0.0
+ dev: false
+
/querystringify@2.2.0:
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
@@ -29021,6 +29061,11 @@ packages:
- utf-8-validate
dev: true
+ /split-on-first@1.1.0:
+ resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==}
+ engines: {node: '>=6'}
+ dev: false
+
/split2@4.2.0:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'}
@@ -29186,6 +29231,11 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
+ /strict-uri-encode@2.0.0:
+ resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==}
+ engines: {node: '>=4'}
+ dev: false
+
/string-env-interpolation@1.0.1:
resolution: {integrity: sha512-78lwMoCcn0nNu8LszbP1UA7g55OeE4v7rCeWnM5B453rnNr4aq+5it3FEYtZrSEiMvHZOZ9Jlqb0OD0M2VInqg==}
dev: true