diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index 9d08a1a..0000000
--- a/.editorconfig
+++ /dev/null
@@ -1,9 +0,0 @@
-root = true
-
-[*]
-charset = utf-8
-indent_style = space
-indent_size = 2
-end_of_line = lf
-insert_final_newline = true
-trim_trailing_whitespace = true
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 746cf57..8dcb2a8 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,6 +1,6 @@
{
- "editor.bracketPairColorization.enabled": true,
- "editor.guides.bracketPairs": true,
+ // "editor.bracketPairColorization.enabled": true,
+ // "editor.guides.bracketPairs": true,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": [
diff --git a/README.md b/README.md
index c6a6e1d..4b604bc 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,6 @@ Because of its minimalist design, Cashonize is a user-friendly wallet, even for
Cashonize nicely displays your NFT collections by collapsing NFTs of the same tokenId, making it ideal for NFT collectors.
Cashonize is a single-address wallet, for privacy-centered users is better to use a HD-wallet.
-Cashonize does not have a display for your transaction history and for now it only supports USD for denomating BCH balances.
## Platforms
diff --git a/package.json b/package.json
index 79e1f96..8114cb0 100644
--- a/package.json
+++ b/package.json
@@ -18,10 +18,12 @@
},
"dependencies": {
"@bitjson/qr-code": "^1.0.2",
+ "@capacitor-community/barcode-scanner": "^4.0.1",
"@capacitor/app": "^5.0.7",
"@capacitor/core": "^5.7.4",
"@download/blockies": "^1.0.3",
"@mainnet-cash/indexeddb-storage": "^2.2.7",
+ "@mainnet-pat/json5-bigint": "^2.2.5",
"@quasar/extras": "^1.16.4",
"@types/blockies": "^0.0.4",
"@vueform/toggle": "^2.1.4",
@@ -29,10 +31,11 @@
"@walletconnect/core": "^2.11.1",
"@walletconnect/web3wallet": "^1.10.1",
"chota": "^0.9.2",
- "mainnet-js": "^2.3.13",
+ "mainnet-js": "^2.4.1",
"pinia": "^2.1.7",
"quasar": "^2.14.3",
"vue": "^3.3.4",
+ "vue-qrcode-reader": "^5.5.7",
"vue-router": "^4.0.12"
},
"devDependencies": {
diff --git a/public/images/qrscan.svg b/public/images/qrscan.svg
new file mode 100644
index 0000000..fecea08
--- /dev/null
+++ b/public/images/qrscan.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/quasar.config.js b/quasar.config.js
index c32f94e..1fbcdb7 100644
--- a/quasar.config.js
+++ b/quasar.config.js
@@ -82,7 +82,14 @@ module.exports = configure(function (/* ctx */) {
// extendViteConf (viteConf) {},
// viteVuePluginOptions: {},
-
+ viteVuePluginOptions: {
+ template: {
+ compilerOptions: {
+ isCustomElement: (tag) => tag === ('qr-code')
+ }
+ }
+ },
+
vitePlugins: [
[
nodePolyfills
diff --git a/src/components/bchWallet.vue b/src/components/bchWallet.vue
index 514a855..413eaf1 100644
--- a/src/components/bchWallet.vue
+++ b/src/components/bchWallet.vue
@@ -4,9 +4,12 @@
import { decodeCashAddress } from "@bitauth/libauth"
import { defineCustomElements } from '@bitjson/qr-code';
import alertDialog from 'src/components/alertDialog.vue'
+ import { CurrencySymbols, CurrencyShortNames, TokenDataFT, TokenDataNFT, TokenData } from 'src/interfaces/interfaces'
import { useStore } from '../stores/store'
import { useSettingsStore } from '../stores/settingsStore'
import { useQuasar } from 'quasar'
+ import QrCodeDialog from './qr/qrCodeScanDialog.vue';
+
const $q = useQuasar()
const store = useStore()
const settingsStore = useSettingsStore()
@@ -14,7 +17,7 @@
const { width } = useWindowSize();
const isMobile = computed(() => width.value < 480)
- const nrTokenCategories = computed(() => store.tokenList?.length)
+ const nrTokenCategories = computed(() => store.tokenList?.filter((token: TokenData) => (token as TokenDataFT).amount > 0n || (token as TokenDataNFT).nfts?.length > 0).length)
const numberFormatter = new Intl.NumberFormat('en-US', {maximumFractionDigits: 8});
@@ -32,8 +35,9 @@
// reactive state
const displayeBchQr = ref(true);
const bchSendAmount = ref(undefined as (number | undefined));
- const usdSendAmount = ref(undefined as (number | undefined));
+ const currencySendAmount = ref(undefined as (number | undefined));
const destinationAddr = ref("");
+ const showQrCodeDialog = ref(false);
function switchAddressTypeQr(){
displayeBchQr.value = !displayeBchQr.value;
@@ -57,29 +61,29 @@
let bchAmount = Number(params.split("amount=")[1]);
if(settingsStore.bchUnit == "sat") bchAmount = Math.round(bchAmount * 100_000_000);
bchSendAmount.value = bchAmount;
- setUsdAmount()
+ setCurrencyAmount()
}
}
- async function setUsdAmount() {
+ async function setCurrencyAmount() {
if(typeof bchSendAmount.value != 'number'){
- usdSendAmount.value = undefined
+ currencySendAmount.value = undefined
return
}
- const newUsdValue = await convert(bchSendAmount.value, settingsStore.bchUnit, "usd");
- usdSendAmount.value = Number(newUsdValue.toFixed(2));
+ const newCurrencyValue = await convert(bchSendAmount.value, settingsStore.bchUnit, settingsStore.currency);
+ currencySendAmount.value = Number(newCurrencyValue.toFixed(2));
}
async function setBchAmount() {
- if(typeof usdSendAmount.value != 'number'){
+ if(typeof currencySendAmount.value != 'number'){
bchSendAmount.value = undefined
return
}
- const newBchValue = await convert(usdSendAmount.value, "usd", settingsStore.bchUnit);
+ const newBchValue = await convert(currencySendAmount.value, settingsStore.currency, settingsStore.bchUnit);
bchSendAmount.value = Number(newBchValue);
}
async function useMaxBchAmount(){
if(store.maxAmountToSend && store.maxAmountToSend[settingsStore.bchUnit]){
bchSendAmount.value = store.maxAmountToSend[settingsStore.bchUnit];
- setUsdAmount()
+ setCurrencyAmount()
}
else{
$q.notify({
@@ -124,7 +128,7 @@
console.log(alertMessage);
// reset fields
bchSendAmount.value = undefined;
- usdSendAmount.value = undefined;
+ currencySendAmount.value = undefined;
destinationAddr.value = "";
} catch(error){
console.log(error)
@@ -136,15 +140,26 @@
})
}
}
+ const qrDecode = (content: string) => {
+ destinationAddr.value = content;
+ }
+ const qrFilter = (content: string) => {
+ const decoded = decodeCashAddress(content);
+ if (typeof decoded === "string" || decoded.prefix !== store.wallet?.networkPrefix) {
+ return "Not a cashaddress on current network";
+ }
+
+ return true;
+ }
- USD balance:
+ {{ CurrencyShortNames[settingsStore.currency] }} balance:
- {{ store.balance && store.balance.usd != undefined ? (store.balance.usd).toFixed(2) + " $": "" }}
+ {{ store.balance && store.balance[settingsStore.currency] != undefined ? (store.balance[settingsStore.currency]).toFixed(2) + ` ${CurrencySymbols[settingsStore.currency]}`: "" }}
@@ -154,18 +169,20 @@
? numberFormatter.format(store.balance[settingsStore.bchUnit] as number) + displayUnitLong : "" }}
-
- , Tokens:
-
- {{ nrTokenCategories != undefined ? nrTokenCategories + " different categories" : ""}}
+
+
+ , Tokens:
+
+ {{ nrTokenCategories + " different categories"}}
+
+
+ Tokens:
+
+ {{ nrTokenCategories + " different categories"}}
+
+
-
- Tokens:
-
- {{ nrTokenCategories != undefined ? nrTokenCategories + " different categories" : ""}}
-
-
BCH address:
copyToClipboard(store.wallet?.address)" style="cursor:pointer;">
@@ -191,21 +208,28 @@
+
+ showQrCodeDialog = false" @decode="qrDecode" :filter="qrFilter"/>
+
\ No newline at end of file
diff --git a/src/components/createTokens.vue b/src/components/createTokens.vue
index 04f8eba..5e0587a 100644
--- a/src/components/createTokens.vue
+++ b/src/components/createTokens.vue
@@ -4,6 +4,7 @@
import alertDialog from 'src/components/alertDialog.vue'
import { useStore } from '../stores/store'
import { useQuasar } from 'quasar'
+import { cachedFetch } from 'src/utils/utils';
const $q = useQuasar()
const store = useStore()
@@ -61,7 +62,10 @@
const fetchLocation = selectedUri.value != "IPFS" ? "https://" + inputField + bcmrLocation : inputField + inputField.slice(7);
try{
console.log("fetching bcmr at "+fetchLocation);
- const response = await fetch(fetchLocation);
+ const response = await cachedFetch(fetchLocation, {
+ storageType: localStorage,
+ duration: 1000 * 60 * 60 * 24, // 1 day
+ });
if(response?.status == 200) validitityCheck.value = true;
const bcmrContent = await response.text();
const hashContent = sha256.hash(utf8ToBin(bcmrContent));
diff --git a/src/components/history/transactionDialog.vue b/src/components/history/transactionDialog.vue
new file mode 100644
index 0000000..4400ab2
--- /dev/null
+++ b/src/components/history/transactionDialog.vue
@@ -0,0 +1,200 @@
+
+
+
+
+
+
+ tokenMetadata = undefined"/>
+
+
+
+ Transaction
+
+
+
+
+ Status:
+ unconfirmed
+ {{ store.currentBlockHeight - historyItem.blockHeight }} confirmations
+ ( Mined in block: {{ historyItem.blockHeight }} )
+
+
+
+ Date:
+ {{ new Date(historyItem.timestamp * 1000).toLocaleString() }}
+
+
+ Balance change:
+ {{ historyItem.valueChange }} {{ unit }}
+
+
+ Size:
+ {{ historyItem.size }} bytes
+
+
+ Fee:
+ {{ feeIncurrency }}{{ currencySymbol }} or {{ historyItem.fee }} sat ( {{ (historyItem.fee / historyItem.size).toFixed(1) }} sat/byte )
+
+
+
+
+ Inputs
+
+
+
+
+ Outputs
+
+
{{ index }}: {{ output.address }}
+
{{ index }}: {{ output.address.split(":")[1] }}
+
+
{{ output.value }} {{ unit }}
+
+ {{ " " + (output.token.amount === 0n ? 1 : Number(output.token.amount) / 10**(store.bcmrRegistries?.[output.token.tokenId]?.token.decimals ?? 0)) }}
+ {{ " " + (bcmrRegistries?.[output.token.tokenId]?.token?.symbol ?? output.token.tokenId.slice(0, 8)) }}
+ NFT
+
+ ({{ (currencyValues[`${output.token.tokenId}-${props.historyItem.timestamp ?? 0}-${output.token.amount}`] === 0 ? '< ' : '') + currencyValues[`${output.token.tokenId}-${props.historyItem.timestamp ?? 0}-${output.token.amount}`] + " " + currencySymbol }})
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/history/walletHistory.vue b/src/components/history/walletHistory.vue
new file mode 100644
index 0000000..d3b7a21
--- /dev/null
+++ b/src/components/history/walletHistory.vue
@@ -0,0 +1,219 @@
+
+
+
+
+
{selectedTransaction = undefined}">
+
+
Loading history ...
+
No transactions in this wallet
+
+
+
+
+ {{ settingsStore.bchUnit.toLocaleUpperCase() }}
+
+ {{ settingsStore.currency.toLocaleUpperCase() }}
+
+
+
+
+
+ Date
+
+
+
+
+
+
+ selectTransaction(transaction)">
+ {{ transaction.timestamp ? "✅" : "⏳" }}
+ {{ transaction.timestamp ? new Date(transaction.timestamp * 1000).toLocaleString().replace("202", "2").replace(",", "") : "Unconf." }}
+ {{ transaction.timestamp ? new Date(transaction.timestamp * 1000).toLocaleString() : "Unconfirmed" }}
+ {{ `+${transaction.valueChange}` }}
+ {{ `${transaction.valueChange}` }}
+ {{ transaction.balance }}
+
+
+
+
+
+ +{{ Number(tokenChange.amount) / 10**(store.bcmrRegistries?.[tokenChange.tokenId]?.token.decimals ?? 0) }}
+ {{ Number(tokenChange.amount) / 10**(store.bcmrRegistries?.[tokenChange.tokenId]?.token.decimals ?? 0) }}
+ {{ " " + (store.bcmrRegistries?.[tokenChange.tokenId]?.token?.symbol ?? tokenChange.tokenId.slice(0, 8)) }}
+
+
+ ({{ tokenChange.amount < 0n ? '' : '+' }}{{ (tokenChangeCurrencyValues[`${tokenChange.tokenId}-${transaction.timestamp ?? 0}-${tokenChange.amount}`] === 0 ? ' <' : '') + tokenChangeCurrencyValues[`${tokenChange.tokenId}-${transaction.timestamp ?? 0}-${tokenChange.amount}`] }} {{ CurrencySymbols[settingsStore.currency] }})
+
+
+
+
+ +{{ tokenChange.nftAmount }}
+ {{ tokenChange.nftAmount }}
+ {{ " " + (store.bcmrRegistries?.[tokenChange.tokenId]?.token?.symbol ?? tokenChange.tokenId.slice(0, 8)) }} NFT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/myTokens.vue b/src/components/myTokens.vue
index 61e8429..f72b977 100644
--- a/src/components/myTokens.vue
+++ b/src/components/myTokens.vue
@@ -9,10 +9,10 @@
Loading tokendata ...
No tokens in this wallet
-
+
\ No newline at end of file
diff --git a/src/components/qr/qrCodeScanDialog.vue b/src/components/qr/qrCodeScanDialog.vue
new file mode 100644
index 0000000..7d68439
--- /dev/null
+++ b/src/components/qr/qrCodeScanDialog.vue
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+ {{ error }}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/qr/qrScannerUi.vue b/src/components/qr/qrScannerUi.vue
new file mode 100644
index 0000000..f6407c1
--- /dev/null
+++ b/src/components/qr/qrScannerUi.vue
@@ -0,0 +1,146 @@
+
+
+
+
+
+
Scan QR Code
+
{{ filterHint }}
+
+
+
+
\ No newline at end of file
diff --git a/src/components/settingsMenu.vue b/src/components/settingsMenu.vue
index ee722da..27339ff 100644
--- a/src/components/settingsMenu.vue
+++ b/src/components/settingsMenu.vue
@@ -1,7 +1,7 @@
@@ -250,7 +265,7 @@
-->
-
showNftImage = true">
+ showNftImage = true" autoPlay loop muted :controls="false">
showNftImage = true">
@@ -296,10 +311,15 @@
@@ -339,4 +362,7 @@
showNftImage = false"/>
+
+ showQrCodeDialog = false" @decode="qrDecode" :filter="qrFilter"/>
+
\ No newline at end of file
diff --git a/src/components/tokenItems/tokenItemFT.vue b/src/components/tokenItems/tokenItemFT.vue
index 3f89a14..e72fce1 100644
--- a/src/components/tokenItems/tokenItemFT.vue
+++ b/src/components/tokenItems/tokenItemFT.vue
@@ -1,15 +1,17 @@
@@ -303,15 +324,20 @@
- Amount:
- {{ numberFormatter.format(toAmountDecimals(tokenData?.amount)) }} {{ tokenMetaData?.token?.symbol }}
+
+
Amount:
+ {{ numberFormatter.format(toAmountDecimals(tokenData?.amount)) }} {{ tokenMetaData?.token?.symbol }}
+
+
Value:
+ {{ tokenPrice }} {{ CurrencySymbols[settingsStore.currency] }}
+
@@ -363,7 +391,12 @@
Send these tokens to
+
+ showQrCodeDialog = false" @decode="qrDecode" :filter="qrFilter"/>
+
\ No newline at end of file
diff --git a/src/components/tokenItems/tokenItemNFT.vue b/src/components/tokenItems/tokenItemNFT.vue
index ed0a925..654be9b 100644
--- a/src/components/tokenItems/tokenItemNFT.vue
+++ b/src/components/tokenItems/tokenItemNFT.vue
@@ -12,6 +12,8 @@
import { useStore } from 'src/stores/store'
import { useSettingsStore } from 'src/stores/settingsStore'
import { useQuasar } from 'quasar'
+ import QrCodeScanDialog from '../qr/qrCodeScanDialog.vue';
+
const $q = useQuasar()
const store = useStore()
const settingsStore = useSettingsStore()
@@ -40,10 +42,11 @@
const startingNumberNFTs = ref(undefined as string | undefined);
const totalNumberNFTs = ref(undefined as number | undefined);
const hasMintingNFT = ref(undefined as boolean | undefined);
+ const showQrCodeDialog = ref(false);
let fetchedMetadataChildren = false
- tokenMetaData.value = store.bcmrRegistries?.[tokenData.value.tokenId] ?? null;
+ tokenMetaData.value = store.bcmrRegistries?.[tokenData.value.tokenId] ?? undefined;
const isSingleNft = computed(() => tokenData.value.nfts?.length == 1);
const nftMetadata = computed(() => {
@@ -385,6 +388,18 @@
color: "red"
})
}
+
+ const qrDecode = (content: string) => {
+ destinationAddr.value = content;
+ }
+ const qrFilter = (content: string) => {
+ const decoded = decodeCashAddress(content);
+ if (typeof decoded === "string" || decoded.prefix !== store.wallet?.networkPrefix || !['p2pkhWithTokens', 'p2shWithTokens'].includes(decoded.type)) {
+ return "Not a tokenaddress on current network";
+ }
+
+ return true;
+ }
@@ -400,7 +415,7 @@
-->
-
showNftImage = true">
+ showNftImage = true" autoPlay loop muted :controls="false">
showNftImage = true">
@@ -452,6 +467,8 @@
auth transfer
+
+ {{ settingsStore.featuredTokens.includes(tokenData.tokenId) ? "★" : "☆" }} favorite
@@ -534,4 +567,8 @@
+
+
+ showQrCodeDialog = false" @decode="qrDecode" :filter="qrFilter"/>
+
\ No newline at end of file
diff --git a/src/components/walletConnect.vue b/src/components/walletConnect.vue
index c7f7127..1e0445c 100644
--- a/src/components/walletConnect.vue
+++ b/src/components/walletConnect.vue
@@ -7,6 +7,7 @@
import { useStore } from 'src/stores/store'
import { useWalletconnectStore } from 'src/stores/walletconnectStore'
import { useQuasar } from 'quasar'
+ import QrCodeDialog from './qr/qrCodeScanDialog.vue';
const $q = useQuasar()
const store = useStore()
const walletconnectStore = useWalletconnectStore()
@@ -19,6 +20,7 @@
const dappUriInput = ref("");
const sessionProposalWC = ref(undefined as any);
const activeSessions = computed(() => walletconnectStore.activeSessions)
+ const showQrCodeDialog = ref(false);
async function connectDappUriInput(){
try {
@@ -94,6 +96,18 @@
const updatedSessions = web3wallet?.getActiveSessions();
walletconnectStore.activeSessions = updatedSessions;
}
+
+ const qrDecode = async (content: string) => {
+ await web3wallet?.core.pairing.pair({ uri: content});
+ }
+ const qrFilter = (content: string) => {
+ const matchV2 = String(content).match(/^wc:([0-9a-fA-F]{64})@(\d+)\?([a-zA-Z0-9\-._~%!$&'()*+,;=:@/?=&]*)$/i);
+ if (!matchV2) {
+ return "Not a valid WalletConnect2 URI";
+ }
+
+ return true;
+}
@@ -102,7 +116,13 @@
Connect New dApp:
-
+
+
+
showQrCodeDialog = true" style="padding: 12px; height: 43px;">
+
+
+
+
@@ -125,6 +145,10 @@
+
+
+ showQrCodeDialog = false" @decode="qrDecode" :filter="qrFilter"/>
+