From a7e87429ee4f54231c612b84e5ad866cb932b3fa Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Fri, 4 Oct 2024 13:42:52 +0200 Subject: [PATCH 01/24] NFC tags read/flash --- src/components/SendTokenDialog.vue | 137 ++++++++++++++++++++++++----- src/pages/WalletPage.vue | 95 +++++++++++++++++--- 2 files changed, 200 insertions(+), 32 deletions(-) diff --git a/src/components/SendTokenDialog.vue b/src/components/SendTokenDialog.vue index 9b8bc787..87c69262 100644 --- a/src/components/SendTokenDialog.vue +++ b/src/components/SendTokenDialog.vue @@ -139,8 +139,7 @@ - +
+ :color="!isV4Token ? 'primary' : 'grey'" + :label="isV4Token ? 'V4' : 'V3'" + class="q-my-sm q-mx-md cursor-pointer" + @click="toggleTokenEncoding" + :outline="isV4Token" + />
@@ -289,12 +288,35 @@ color="grey" icon="delete" size="md" - @click="showDeleteDialog = true" + @click=" + showDeleteDialog = true; + closeCardScanner(); + " flat > Delete from history - + Flash to NFC tag + + + Close
@@ -356,12 +378,21 @@ import { Buffer } from "buffer"; import { useCameraStore } from "src/stores/camera"; import { useP2PKStore } from "src/stores/p2pk"; import TokenInformation from "components/TokenInformation.vue"; -import { getDecodedToken, getEncodedTokenV4, getEncodedToken } from "@cashu/cashu-ts"; +import { + getDecodedToken, + getEncodedTokenV4, + getEncodedToken, +} from "@cashu/cashu-ts"; import { mapActions, mapState, mapWritableState } from "pinia"; import ChooseMint from "components/ChooseMint.vue"; import { UR, UREncoder } from "@gandlaf21/bc-ur"; - +import { + notifyError, + notifySuccess, + notify, + notifyWarning, +} from "src/js/notify.ts"; export default defineComponent({ name: "SendTokenDialog", mixins: [windowMixin], @@ -394,6 +425,7 @@ export default defineComponent({ framentInervalSlow: 500, fragmentSpeedLabel: "F", isV4Token: false, + scanningCard: false, }; }, computed: { @@ -403,7 +435,11 @@ export default defineComponent({ ]), ...mapWritableState(useSendTokensStore, ["sendData"]), ...mapWritableState(useCameraStore, ["camera", "hasCamera"]), - ...mapState(useUiStore, ["tickerShort", "canPasteFromClipboard", "globalMutexLock"]), + ...mapState(useUiStore, [ + "tickerShort", + "canPasteFromClipboard", + "globalMutexLock", + ]), ...mapState(useMintsStore, [ "activeProofs", "activeUnit", @@ -596,17 +632,13 @@ export default defineComponent({ // if it starts with 'cashuB', it is a v4 token if (this.sendData.tokensBase64.startsWith("cashuA")) { try { - this.sendData.tokensBase64 = getEncodedTokenV4(decodedToken) + this.sendData.tokensBase64 = getEncodedTokenV4(decodedToken); } catch { console.log("### Could not encode token to V4"); - this.sendData.tokensBase64 = getEncodedToken( - decodedToken - ); + this.sendData.tokensBase64 = getEncodedToken(decodedToken); } } else { - this.sendData.tokensBase64 = getEncodedToken( - decodedToken - ); + this.sendData.tokensBase64 = getEncodedToken(decodedToken); } }, deleteThisToken: function () { @@ -615,6 +647,71 @@ export default defineComponent({ this.showDeleteDialog = false; this.clearAllWorkers(); }, + writeTokensToCard: function () { + if (!this.scanningCard) { + try { + this.ndef = new window.NDEFReader(); + this.controller = new AbortController(); + const signal = this.controller.signal; + this.ndef + .scan({ signal }) + .then(() => { + console.log("> Scan started"); + + this.ndef.onreadingerror = (error) => { + console.error(`Cannot read NDEF data! ${error}`); + notifyError("Cannot read data from the NFC tag"); + this.controller.abort(); + this.scanningCard = false; + }; + + this.ndef.onreading = ({ message, serialNumber }) => { + console.log(`Read card ${serialNumber}`); + notify(`Serial: ${serialNumber}`); + this.controller.abort(); + this.scanningCard = false; + try { + const arrBuffer = new TextEncoder().encode( + this.sendData.tokensBase64 + ); + this.ndef + .write(arrBuffer, { + overwrite: true, + }) + .then(() => { + console.log("Successfully flashed tokens to card!"); + notifySuccess("Successfully flashed tokens to card!"); + this.showSendTokens = false; + }) + .catch((err) => { + console.error(`Argh! ${error}`); + notifyError(`Argh! ${err}`); + }); + } catch (err) { + console.error(`Argh! ${error}`); + notifyError(`Argh! ${err}`); + } + }; + this.scanningCard = true; + }) + .catch((error) => { + console.error(`Argh! ${error}`); + notifyError(`Argh! ${error}`); + this.scanningCard = false; + }); + notifyWarning("THIS WILL OVERWRITE YOUR CARD!"); + } catch (error) { + console.error(`Argh! ${error}`); + notifyError(`Argh! ${error}`); + this.scanningCard = false; + } + } + }, + closeCardScanner: function () { + console.log("Closing scanner!"); + this.controller.abort(); + this.scanningCard = false; + }, lockTokens: async function () { let sendAmount = this.sendData.amount; // if unit is USD, multiply by 100 diff --git a/src/pages/WalletPage.vue b/src/pages/WalletPage.vue index 036b128b..2f316b67 100644 --- a/src/pages/WalletPage.vue +++ b/src/pages/WalletPage.vue @@ -3,6 +3,37 @@
+
+
+ +
+
+ + + +
+
@@ -20,17 +51,6 @@ Receive
-
- -
{ + console.log("> Scan started"); + + this.ndef.addEventListener("readingerror", () => { + console.error("Argh! Cannot read data from the NFC tag."); + notifyError("Argh! Cannot read data from the NFC tag."); + this.controller.abort(); + this.scanningCard = false; + }); + + this.ndef.addEventListener("reading", ({ message, serial }) => { + notify(`Serial: ${serial}`); + try { + const tokensBase64 = new TextDecoder().decode( + message.records[0].data + ); + //getDecodedToken(tokensBase64); + this.receiveData.tokensBase64 = tokensBase64; + this.redeem(); + } catch (err) { + console.error(`Something went wrong! ${error}`); + notifyError(`Something went wrong! ${err}`); + } + this.controller.abort(); + this.scanningCard = false; + }); + this.scanningCard = true; + }) + .catch((error) => { + console.error(`Argh! ${error}`); + notifyError(`Argh! ${error}`); + }); + } catch (error) { + console.error(`Argh! ${error}`); + notifyError(`Argh! ${error}`); + } + } else { + this.controller.abort(); + this.scanningCard = false; + } + }, // TOKEN METHODS decodeToken: function (encoded_token) { try { From fa69c6ad601f89e5dbf4b2e0ec79fd4d9b6ace94 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Tue, 8 Oct 2024 14:48:12 +0200 Subject: [PATCH 02/24] better specify record type + fix error handdler --- src/components/SendTokenDialog.vue | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/SendTokenDialog.vue b/src/components/SendTokenDialog.vue index 87c69262..6d3681eb 100644 --- a/src/components/SendTokenDialog.vue +++ b/src/components/SendTokenDialog.vue @@ -674,8 +674,13 @@ export default defineComponent({ const arrBuffer = new TextEncoder().encode( this.sendData.tokensBase64 ); + const ndefRecord = new NDEFRecord({ + data: arrBuffer, + mediaType: "text/plain", + recordType: "text", + }); this.ndef - .write(arrBuffer, { + .write([ndefRecord], { overwrite: true, }) .then(() => { @@ -684,7 +689,7 @@ export default defineComponent({ this.showSendTokens = false; }) .catch((err) => { - console.error(`Argh! ${error}`); + console.error(`Argh! ${err}`); notifyError(`Argh! ${err}`); }); } catch (err) { From 19d3219cfa7c80b96689fb2fbc87312d1562f47e Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Tue, 8 Oct 2024 15:35:11 +0200 Subject: [PATCH 03/24] mime type text/plain --- src/components/SendTokenDialog.vue | 13 +++++++----- src/pages/WalletPage.vue | 33 ++++++++++++++++-------------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/components/SendTokenDialog.vue b/src/components/SendTokenDialog.vue index 6d3681eb..3cf27356 100644 --- a/src/components/SendTokenDialog.vue +++ b/src/components/SendTokenDialog.vue @@ -676,13 +676,16 @@ export default defineComponent({ ); const ndefRecord = new NDEFRecord({ data: arrBuffer, + recordType: "mime", mediaType: "text/plain", - recordType: "text", }); this.ndef - .write([ndefRecord], { - overwrite: true, - }) + .write( + { records: [ndefRecord] }, + { + overwrite: true, + } + ) .then(() => { console.log("Successfully flashed tokens to card!"); notifySuccess("Successfully flashed tokens to card!"); @@ -693,7 +696,7 @@ export default defineComponent({ notifyError(`Argh! ${err}`); }); } catch (err) { - console.error(`Argh! ${error}`); + console.error(`Argh! ${err}`); notifyError(`Argh! ${err}`); } }; diff --git a/src/pages/WalletPage.vue b/src/pages/WalletPage.vue index 2f316b67..651dedff 100644 --- a/src/pages/WalletPage.vue +++ b/src/pages/WalletPage.vue @@ -396,22 +396,25 @@ export default { this.scanningCard = false; }); - this.ndef.addEventListener("reading", ({ message, serial }) => { - notify(`Serial: ${serial}`); - try { - const tokensBase64 = new TextDecoder().decode( - message.records[0].data - ); - //getDecodedToken(tokensBase64); - this.receiveData.tokensBase64 = tokensBase64; - this.redeem(); - } catch (err) { - console.error(`Something went wrong! ${error}`); - notifyError(`Something went wrong! ${err}`); + this.ndef.addEventListener( + "reading", + ({ message, serialNumber }) => { + notify(`Serial: ${serialNumber}`); + try { + const tokensBase64 = new TextDecoder().decode( + message.records[0].data + ); + //getDecodedToken(tokensBase64); + this.receiveData.tokensBase64 = tokensBase64; + this.redeem(); + } catch (err) { + console.error(`Something went wrong! ${error}`); + notifyError(`Something went wrong! ${err}`); + } + this.controller.abort(); + this.scanningCard = false; } - this.controller.abort(); - this.scanningCard = false; - }); + ); this.scanningCard = true; }) .catch((error) => { From a868ef63e5625b0c0b5eb0555c9041aac69ad773 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Tue, 8 Oct 2024 15:56:16 +0200 Subject: [PATCH 04/24] fix error handler --- src/pages/WalletPage.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/WalletPage.vue b/src/pages/WalletPage.vue index 651dedff..9d106b7c 100644 --- a/src/pages/WalletPage.vue +++ b/src/pages/WalletPage.vue @@ -408,7 +408,7 @@ export default { this.receiveData.tokensBase64 = tokensBase64; this.redeem(); } catch (err) { - console.error(`Something went wrong! ${error}`); + console.error(`Something went wrong! ${err}`); notifyError(`Something went wrong! ${err}`); } this.controller.abort(); From 49953cdb411d4510d9412e9a4a9969c9ee42444f Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sat, 12 Oct 2024 11:57:31 +0200 Subject: [PATCH 05/24] extract and add mint from token --- src/pages/WalletPage.vue | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/pages/WalletPage.vue b/src/pages/WalletPage.vue index 9d106b7c..4e845d3e 100644 --- a/src/pages/WalletPage.vue +++ b/src/pages/WalletPage.vue @@ -26,10 +26,10 @@ outline color="primary" flat - @click="startScanner" + @click="toggleScanner" >
@@ -378,7 +378,19 @@ export default { ...mapActions(useCameraStore, ["closeCamera", "showCamera"]), ...mapActions(useNWCStore, ["listenToNWCCommands"]), ...mapActions(useNPCStore, ["generateNPCConnection", "claimAllTokens"]), - startScanner: function () { + knowThisMintOfTokenJson: function (tokenJson) { + const mintStore = useMintsStore(); + // check if we have all mints + for (var i = 0; i < tokenJson.token.length; i++) { + if ( + !mintStore.mints.map((m) => m.url).includes(token.getMint(tokenJson)) + ) { + return false; + } + } + return true; + }, + toggleScanner: function () { if (!this.scanningCard) { try { this.ndef = new window.NDEFReader(); @@ -406,6 +418,15 @@ export default { ); //getDecodedToken(tokensBase64); this.receiveData.tokensBase64 = tokensBase64; + const tokenJson = token.decode( + this.receiveData.tokensBase64 + ); + if (tokenJson == undefined) { + throw new Error("unreadable token"); + } + if (!this.knowThisMintOfTokenJson(tokenJson)) { + this.addMint({ url: token.getMint(tokenJson) }); + } this.redeem(); } catch (err) { console.error(`Something went wrong! ${err}`); From 6872f59544cc34edcd74d3aec25b0097de2d6e43 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sat, 12 Oct 2024 16:14:01 +0200 Subject: [PATCH 06/24] disable button if NDEF unsupported. --- src/components/SendTokenDialog.vue | 9 +++++++-- src/pages/WalletPage.vue | 8 ++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/components/SendTokenDialog.vue b/src/components/SendTokenDialog.vue index 3cf27356..64527a40 100644 --- a/src/components/SendTokenDialog.vue +++ b/src/components/SendTokenDialog.vue @@ -297,7 +297,7 @@ Delete from history - Flash to NFC tag + {{ + isNdefSupported() ? "Flash to NFC card" : "NDEF unsupported" + }} @@ -720,6 +722,9 @@ export default defineComponent({ this.controller.abort(); this.scanningCard = false; }, + isNdefSupported: function () { + return "NDEFReader" in globalThis; + }, lockTokens: async function () { let sendAmount = this.sendData.amount; // if unit is USD, multiply by 100 diff --git a/src/pages/WalletPage.vue b/src/pages/WalletPage.vue index 4e845d3e..ac7c15fd 100644 --- a/src/pages/WalletPage.vue +++ b/src/pages/WalletPage.vue @@ -20,6 +20,7 @@
+ {{ + isNdefSupported() ? "Read from NFC card" : "NDEF unsupported" + }} @@ -451,6 +455,10 @@ export default { this.scanningCard = false; } }, + // Do we show the NFC button? + isNdefSupported: function () { + return "NDEFReader" in globalThis; + }, // TOKEN METHODS decodeToken: function (encoded_token) { try { From 78ee6306151c2d3ffe18fd46b65dba409fca33db Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Tue, 15 Oct 2024 21:35:20 +0200 Subject: [PATCH 07/24] ndefSupported as a var instead of func --- src/components/SendTokenDialog.vue | 8 +++----- src/pages/WalletPage.vue | 9 +++------ 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/components/SendTokenDialog.vue b/src/components/SendTokenDialog.vue index 64527a40..3f42182f 100644 --- a/src/components/SendTokenDialog.vue +++ b/src/components/SendTokenDialog.vue @@ -297,7 +297,7 @@ Delete from history {{ - isNdefSupported() ? "Flash to NFC card" : "NDEF unsupported" + ndefSupported ? "Flash to NFC card" : "NDEF unsupported" }}