diff --git a/.electron-builder.config.cjs b/.electron-builder.config.cjs index a0f463e4..ef910154 100644 --- a/.electron-builder.config.cjs +++ b/.electron-builder.config.cjs @@ -9,7 +9,9 @@ * @see https://www.electron.build/configuration/configuration */ module.exports = async function () { - const {getVersion} = await import('./version/getVersion.mjs'); + const { + getVersion + } = await import('./version/getVersion.mjs'); return { directories: { @@ -23,7 +25,27 @@ module.exports = async function () { productName: "Clorio Wallet", // Specify linux target just for disabling snap compilation linux: { - target: 'deb', + target: [{ + target: "deb", + }, + { + target: "AppImage", + }, + ] + }, + dmg: { + contents: [{ + x: 340, + y: 270, + type: 'file', + }, + { + x: 560, + y: 270, + type: 'link', + path: '/Applications', + }, + ], }, }; }; diff --git a/build/background.png b/build/background.png new file mode 100644 index 00000000..cbb1ae53 Binary files /dev/null and b/build/background.png differ diff --git a/buildResources/background.png b/buildResources/background.png new file mode 100644 index 00000000..cbb1ae53 Binary files /dev/null and b/buildResources/background.png differ diff --git a/package.json b/package.json index 07ab2ba5..f0233f2d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "clorio-wallet", "description": "Clorio wallet", - "version": "2.1.1", + "version": "2.1.2", "private": true, "main": "packages/main/dist/index.cjs", "author": { @@ -19,6 +19,7 @@ "compile:mac": "yarn build && ./node_modules/.bin/electron-builder --config .electron-builder.config.cjs --publish never", "compile:all": "CSC_IDENTITY_AUTO_DISCOVERY=false yarn build && ./node_modules/.bin/electron-builder -mwl --config .electron-builder.config.cjs --publish never", "compile:linux": "CSC_IDENTITY_AUTO_DISCOVERY=false yarn build && ./node_modules/.bin/electron-builder -l --config .electron-builder.config.cjs --publish never", + "compile:ml": "CSC_IDENTITY_AUTO_DISCOVERY=false yarn build && ./node_modules/.bin/electron-builder -ml --config .electron-builder.config.cjs --publish never", "test": "npm run test:main && npm run test:preload && npm run test:renderer && npm run test:e2e", "test:e2e": "npm run build && vitest run", "test:main": "vitest run -r packages/main --passWithNoTests", diff --git a/packages/renderer/src/App.scss b/packages/renderer/src/App.scss index 9ef17581..77dc0dda 100644 --- a/packages/renderer/src/App.scss +++ b/packages/renderer/src/App.scss @@ -9,6 +9,7 @@ @import "./styles/__new_design"; @import "./styles/__accountAvatar"; @import "./styles/__sidebar"; +@import "./styles/__zkapps"; @import url("https://fonts.googleapis.com/css2?family=Mada:wght@200;300;400;500;600;700;900&display=swap"); a { diff --git a/packages/renderer/src/components/UI/epochBar/EpochBar.tsx b/packages/renderer/src/components/UI/epochBar/EpochBar.tsx index 78b2a8fa..fdace3e7 100644 --- a/packages/renderer/src/components/UI/epochBar/EpochBar.tsx +++ b/packages/renderer/src/components/UI/epochBar/EpochBar.tsx @@ -13,6 +13,7 @@ interface RemainingTime { const EpochBar = () => { const {settings} = useNetworkSettingsContext(); + const [epochError, setEpochError] = useState(false); const [epochData, setEpochData] = useState({ slot: 0, epoch: 0, @@ -42,17 +43,21 @@ const EpochBar = () => { }, []); const fetchEpochData = async () => { - const data = await fetch(settings?.epochUrl || '') - .then(response => response.json()) - .then(data => data); - const realSlot = +data.slot % (MAX_SLOT + 1); - const lastTime = ((MAX_SLOT - +realSlot) * SLOT_DURATION) / 1000; - setEpochData(data); - setRemainingTime(calculateTime(lastTime)); - setPercentage(parseInt(((100 * +realSlot) / MAX_SLOT).toFixed(0))); + try { + const data = await fetch(settings?.epochUrl || '') + .then(response => response.json()) + .then(data => data); + const realSlot = +data.slot % (MAX_SLOT + 1); + const lastTime = ((MAX_SLOT - +realSlot) * SLOT_DURATION) / 1000; + setEpochData(data); + setRemainingTime(calculateTime(lastTime)); + setPercentage(parseInt(((100 * +realSlot) / MAX_SLOT).toFixed(0))); + } catch (error) { + setEpochError(true); + } }; - if (!settings?.epochUrl) { + if (!settings?.epochUrl || epochError) { return (
{ + fetchAndSetNonce(); + }, [showDelegationConfirmation]); + useEffect(() => { if (fromRef.current) { setFromTextWidth(fromRef.current.offsetWidth - 250); @@ -67,6 +73,21 @@ export default function ConfirmZkappDelegation() { sendResponse('clorio-error', ERROR_CODES.userRejectedRequest); }; + const fetchAndSetNonce = async () => { + if (showDelegationConfirmation) { + const {data: nonceData} = await fetchNonce({variables: {publicKey: wallet.address}}); + if (nonceData?.accountByKey?.usableNonce || nonceData?.accountByKey?.usableNonce === 0) { + setZkappState(state => ({ + ...state, + transactionData: { + ...state.transactionData, + nonce: nonceData.accountByKey.usableNonce, + }, + })); + } + } + }; + // Check with BigJs if the balance is enough const checkBalance = (fee: number | string) => { if (getBalance) { @@ -110,18 +131,10 @@ export default function ConfirmZkappDelegation() { sendResponse('error', {error: nonceError}); return; } - // TODO: Add custom nonce - // Get the nonce - - const nonceResponse = await fetchNonce({variables: {publicKey: address[0]}}); - const nonce = nonceResponse?.data?.accountByKey?.usableNonce; - if (!nonce && nonce !== 0) { - throw new Error('Nonce not found'); - } // Prepare the stake data const stakeData = { ...transactionData, - nonce, + // nonce, from: address[0], fee: toNanoMINA(transactionData.fee), }; @@ -129,19 +142,19 @@ export default function ConfirmZkappDelegation() { const clientInstance = await client(); const signedTx = await clientInstance.signStakeDelegation(stakeData, privateKey); const hashedTx = await clientInstance.hashStakeDelegation(signedTx); - await broadcastDelegationTx(signedTx); - sendResponse('clorio-staked-delegation', {hash: hashedTx}); - onPostSign(); + await broadcastDelegationTx(signedTx, hashedTx); } catch (error) { console.error('Error in onConfirm:', error); toast.error('An error occurred while confirming the transaction'); } }; - const broadcastDelegationTx = async (signedTx: SignedTx) => { + const broadcastDelegationTx = async (signedTx: SignedTx, hashedTx?: any) => { await broadcastDelegation({ variables: {input: signedTx.data, signature: signedTx.signature}, }); + sendResponse('clorio-staked-delegation', {hash: hashedTx || ''}); + onPostSign(); }; const onPostSign = () => { @@ -186,10 +199,17 @@ export default function ConfirmZkappDelegation() {
{showPassword ? ( - setShowPassword(false)} - onSuccess={onConfirm} - /> + isLedgerEnabled ? ( + setShowPassword(false)} + onSuccess={broadcastDelegationTx} + /> + ) : ( + setShowPassword(false)} + onSuccess={onConfirm} + /> + ) ) : (
void; + onSuccess: (signedTx: unknown) => void; onClose: () => void; } export default function ConfirmZkappLedger({onSuccess, onClose}: IConfirmZkappLedger) { @@ -31,7 +31,6 @@ export default function ConfirmZkappLedger({onSuccess, onClose}: IConfirmZkappLe */ const sendLedgerTransaction = async () => { try { - checkMemoLength(transactionData.memo); await isMinaAppOpen(); const senderAccount = ledgerAccount || 0; const signature = await createAndSignLedgerTransaction({ @@ -47,7 +46,7 @@ export default function ConfirmZkappLedger({onSuccess, onClose}: IConfirmZkappLe }, nonce: +transactionData.nonce || 0, }); - completeSignature(reEncodeRawSignature(signature.signature)); + completeSignature(signature.signature); } catch (e) { setShowError(true); setErrorMessage(e.message || 'An error occurred while loading hardware wallet'); @@ -57,15 +56,15 @@ export default function ConfirmZkappLedger({onSuccess, onClose}: IConfirmZkappLe const completeSignature = signature => { const data = { - signature, + rawSignature: signature, publicKey: transactionData.from, data: { from: transactionData.from, to: transactionData.to, - amount: toNanoMINA(transactionData.amount), - fee: toNanoMINA(transactionData.fee), + amount: '' + toNanoMINA(transactionData.amount), + fee: '' + toNanoMINA(transactionData.fee), memo: transactionData.memo, - nonce: +transactionData.nonce || 0, + nonce: '' + transactionData.nonce || 0, }, }; return onSuccess(data); diff --git a/packages/renderer/src/components/UI/modals/zkAppIntegration/ConfirmZkappLedgerDelegation.tsx b/packages/renderer/src/components/UI/modals/zkAppIntegration/ConfirmZkappLedgerDelegation.tsx new file mode 100644 index 00000000..c310df10 --- /dev/null +++ b/packages/renderer/src/components/UI/modals/zkAppIntegration/ConfirmZkappLedgerDelegation.tsx @@ -0,0 +1,88 @@ +import {useEffect, useState} from 'react'; +import {useRecoilValue} from 'recoil'; +import {walletState, zkappState} from '/@/store'; +import { + createLedgerDelegationTransaction, + isMinaAppOpen, + signTransaction, +} from '/@/tools/ledger/ledger'; +import {toast} from 'react-toastify'; +import {toNanoMINA} from '/@/tools'; +import LedgerSignError from './LedgerSignError'; +import LedgerSingPending from './LedgerSingPending'; + +interface IConfirmZkappLedgerDelegation { + onSuccess: (signedTx: unknown) => void; + onClose: () => void; +} +export default function ConfirmZkappLedgerDelegation({ + onSuccess, + onClose, +}: IConfirmZkappLedgerDelegation) { + const {transactionData} = useRecoilValue(zkappState); + const {ledgerAccount} = useRecoilValue(walletState); + const [showError, setShowError] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); + + useEffect(() => { + sendLedgerTransaction(); + }, []); + + /** + * Sign transaction with Ledger + */ + const sendLedgerTransaction = async () => { + try { + await isMinaAppOpen(); + const senderAccount = ledgerAccount || 0; + const transactionToSend = await createLedgerDelegationTransaction({ + senderAddress: transactionData.from, + receiverAddress: transactionData.to, + fee: toNanoMINA(transactionData.fee), + nonce: +transactionData.nonce || 0, + senderAccount, + }); + + const signature = await signTransaction(transactionToSend); + completeSignature(signature.signature); + } catch (e) { + console.log('🚀 ~ sendLedgerTransaction ~ e:', e); + setShowError(true); + setErrorMessage(e.message || 'An error occurred while loading hardware wallet'); + toast.error(e.message || 'An error occurred while loading hardware wallet'); + } + }; + + const completeSignature = signature => { + const data = { + signature: {rawSignature: signature}, + publicKey: transactionData.from, + data: { + from: transactionData.from, + to: transactionData.to, + fee: '' + toNanoMINA(transactionData.fee), + nonce: '' + transactionData.nonce || 0, + }, + }; + return onSuccess(data); + }; + + return ( +
+
+
+
+ {showError ? ( + + ) : ( + + )} +
+
+
+
+ ); +} diff --git a/packages/renderer/src/components/UI/modals/zkAppIntegration/ConfirmZkappPayment.tsx b/packages/renderer/src/components/UI/modals/zkAppIntegration/ConfirmZkappPayment.tsx index fe5b111c..8899a198 100644 --- a/packages/renderer/src/components/UI/modals/zkAppIntegration/ConfirmZkappPayment.tsx +++ b/packages/renderer/src/components/UI/modals/zkAppIntegration/ConfirmZkappPayment.tsx @@ -12,7 +12,12 @@ import {signTransaction} from '../../../../tools/utils'; import PasswordDecrypt from '../../../PasswordDecrypt'; import {toast} from 'react-toastify'; import {mnemonicToPrivateKey} from '../../../../../../preload/src/bip'; -import {client, createPaymentInputFromPayload, createSignatureInputFromSignature, toNanoMINA} from '/@/tools'; +import { + client, + createPaymentInputFromPayload, + createSignatureInputFromSignature, + toNanoMINA, +} from '/@/tools'; import {ERROR_CODES} from '/@/tools/zkapp'; import ConfirmZkappLedger from './ConfirmZkappLedger'; import TransactionData from './TransactionData'; @@ -39,11 +44,23 @@ export default function ConfirmZkappPayment() { }); useEffect(() => { + fetchAndSetNonce(); + }, [showPaymentConfirmation]); + + const fetchAndSetNonce = async () => { if (showPaymentConfirmation) { - const address = getAccountAddress(); - fetchNonce({variables: {publicKey: address[0]}}); + const {data: nonceData} = await fetchNonce({variables: {publicKey: wallet.address}}); + if (nonceData?.accountByKey?.usableNonce || nonceData?.accountByKey?.usableNonce === 0) { + setZkappState(state => ({ + ...state, + transactionData: { + ...state.transactionData, + nonce: nonceData.accountByKey.usableNonce, + }, + })); + } } - }, [showPaymentConfirmation]); + }; const onClose = () => { setZkappState(zkappInitialState); @@ -57,7 +74,7 @@ export default function ConfirmZkappPayment() { const address = getAccountAddress(); const balance = getBalance(address[0]); const available = +(balance?.liquidUnconfirmed || 0); - if (+available > 0 && (+Big(available).sub(fee)) >= 0) { + if (+available > 0 && +Big(available).sub(fee) >= 0) { return true; } } @@ -76,10 +93,8 @@ export default function ConfirmZkappPayment() { sendResponse('error', {error: nonceError}); return; } - const nonce = nonceData?.accountByKey?.usableNonce; const signedTx = await signTransaction(privateKey, { ...transactionData, - nonce, from: address[0], amount: toNanoMINA(transactionData.amount), fee: toNanoMINA(transactionData.fee), @@ -88,8 +103,13 @@ export default function ConfirmZkappPayment() { }; const completePayment = async (signedTx: unknown) => { - const hashedTx = await (await client()).hashPayment(signedTx); - sendResponse('clorio-signed-payment', {hash: hashedTx}); + if (isLedgerEnabled) { + // TODO: Implement ledger hash from BE + sendResponse('clorio-signed-payment', {hash: ''}); + } else { + const hashedTx = await (await client()).hashPayment(signedTx); + sendResponse('clorio-signed-payment', {hash: hashedTx}); + } toast.success('Transaction signed successfully, waiting for broadcast.'); setZkappState(state => ({ ...state, @@ -101,7 +121,12 @@ export default function ConfirmZkappPayment() { }; const broadcastPayment = async (signedTx: unknown) => { - const signatureInput = createSignatureInputFromSignature(signedTx.signature); + let signatureInput; + if (isLedgerEnabled) { + signatureInput = {rawSignature: signedTx.rawSignature}; + } else { + signatureInput = createSignatureInputFromSignature(signedTx.signature); + } const paymentInput = createPaymentInputFromPayload(signedTx.data); await broadcastTransaction({ variables: {input: paymentInput, signature: signatureInput}, diff --git a/packages/renderer/src/components/UI/modals/zkAppIntegration/LedgerSingPending.tsx b/packages/renderer/src/components/UI/modals/zkAppIntegration/LedgerSingPending.tsx index edac503b..959ad4ab 100644 --- a/packages/renderer/src/components/UI/modals/zkAppIntegration/LedgerSingPending.tsx +++ b/packages/renderer/src/components/UI/modals/zkAppIntegration/LedgerSingPending.tsx @@ -1,10 +1,19 @@ import TransactionData from './TransactionData'; -export default function LedgerSingPending({transactionData}: {transactionData: any}) { +export default function LedgerSingPending({ + transactionData, + isDelegation, +}: { + transactionData: any; + isDelegation?: boolean; +}) { return ( <>
- +

Waiting for the Ledger device to sign the transaction. diff --git a/packages/renderer/src/components/UI/modals/zkAppIntegration/ZkappConnectedApps.tsx b/packages/renderer/src/components/UI/modals/zkAppIntegration/ZkappConnectedApps.tsx new file mode 100644 index 00000000..5fa575d1 --- /dev/null +++ b/packages/renderer/src/components/UI/modals/zkAppIntegration/ZkappConnectedApps.tsx @@ -0,0 +1,106 @@ +import {useState} from 'react'; +import {AlertOctagon} from 'react-feather'; +import Hoc from '../../Hoc'; +import Button from '../../Button'; +import {useRecoilValue} from 'recoil'; +import {connectedSitesState} from '/@/store'; +import NewZkappConnectionModal from './NewZkappConnectionModal'; +import Input from '../../input/Input'; +import {toast} from 'react-toastify'; +const GOOGLE_FAVICON_URL = 'https://s2.googleusercontent.com/s2/favicons?domain_url='; + +const initialZkappData = { + name: '', + url: '', +}; + +export const ZkappConnectedApps = () => { + const [showNewZkapp, setShowNewZkapp] = useState(false); + const {sites} = useRecoilValue(connectedSitesState); + const [newZkapp, setNewZkapp] = useState(initialZkappData); + + const openLink = (url: string) => { + (window.ipcBridge as any).invoke('open-win', JSON.stringify({url})); + }; + + const isValidUrl = () => { + try { + new URL(newZkapp.url); + return true; + } catch (e) { + return false; + } + }; + + const onSubmit = () => { + if (!isValidUrl()) { + toast.info('Invalid URL'); + return; + } + openLink(newZkapp.url); + setNewZkapp(initialZkappData); + setShowNewZkapp(false); + }; + + return ( +

+
+ +

+ Connect only to trusted Zkapps.
+ Do not enter your private keys on untrusted sites or suspicious sites. +

+
+ +
+
Open Zkapp
+
+ { + setNewZkapp({...newZkapp, url: e.target.value}); + }} + /> +
+
+
Connected Zkapps
+
+ {sites.map(({source, title}: {source: string; title: string}) => ( +
openLink(source)} + > + favicon +

{title}

+

{source}

+
+ ))} +
+
+ + +
+ ); +}; diff --git a/packages/renderer/src/components/UI/sidebar/NetworkSettings.tsx b/packages/renderer/src/components/UI/sidebar/NetworkSettings.tsx index d47acc00..085a6978 100644 --- a/packages/renderer/src/components/UI/sidebar/NetworkSettings.tsx +++ b/packages/renderer/src/components/UI/sidebar/NetworkSettings.tsx @@ -12,6 +12,7 @@ import {useRecoilState} from 'recoil'; import {networkState} from '/@/store'; import {ConnectedZkapps} from './ConnectedZkapps'; import {NetConfig, sendResponse} from '/@/tools/mina-zkapp-bridge'; +import isElectron from 'is-electron'; export default function NetworkSettings({ currentNetwork, @@ -79,7 +80,7 @@ export default function NetworkSettings({ >

Settings

-

Current version: 2.1.1

+

Current version: 2.1.2


@@ -133,7 +134,7 @@ export default function NetworkSettings({
- {lockSession && ( + {lockSession && isElectron() && (
{ const networksFound = availableNetworks.filter(network => network.chainId === chainId); if (networksFound.length === 0) { @@ -358,7 +357,6 @@ export default function ZkappIntegration() { })); }; - // TODO: Implement stake delegation const stakeDelegation = async (data: any) => { console.log('Received stake-delegation'); if (!config.isAuthenticated) { diff --git a/packages/renderer/src/components/ledgerLogin/LedgerGetAddress.tsx b/packages/renderer/src/components/ledgerLogin/LedgerGetAddress.tsx index 72ade21d..d1863999 100644 --- a/packages/renderer/src/components/ledgerLogin/LedgerGetAddress.tsx +++ b/packages/renderer/src/components/ledgerLogin/LedgerGetAddress.tsx @@ -8,10 +8,10 @@ import LedgerConfirmAddress from './LedgerConfirmAddress'; import type {IWalletIdData} from '../../types/WalletIdData'; import LedgerLoader from '../UI/ledgerLogin/LedgerLoader'; import {ArrowLeft} from 'react-feather'; -import {getPublicKey} from '/@/tools/ledger/ledgerElectronAPI'; import {useNavigate} from 'react-router-dom'; import {useSetRecoilState} from 'recoil'; import {configState, walletState} from '/@/store'; +import { getPublicKey } from '/@/tools/ledger/ledger'; interface IProps { accountNumber?: number; diff --git a/packages/renderer/src/components/transactionsTable/TransactionIcon.tsx b/packages/renderer/src/components/transactionsTable/TransactionIcon.tsx index 7e6f030e..195f5331 100644 --- a/packages/renderer/src/components/transactionsTable/TransactionIcon.tsx +++ b/packages/renderer/src/components/transactionsTable/TransactionIcon.tsx @@ -1,10 +1,4 @@ -import { - Check, - ChevronRight, - ChevronsDown, - ChevronsUp, - AlertTriangle, -} from 'react-feather'; +import {Check, ChevronRight, ChevronsDown, ChevronsUp, AlertTriangle} from 'react-feather'; const TransactionIcon = ( txType: string, @@ -12,9 +6,20 @@ const TransactionIcon = ( receiver: string, userAddress: string, isScam: boolean, + failed: boolean, + reason?: string, ) => { if (isScam) { - return ; + return ( + + ); + } + // TODO: Change icon + if (failed) { + return ; } if (txType === 'delegation') { return ; @@ -22,9 +27,20 @@ const TransactionIcon = ( if (receiver === sender) { return ; } else if (userAddress === sender) { - return ; + return ( + + ); } else if (userAddress === receiver) { - return ; + return ( + + ); } else { return ; } diff --git a/packages/renderer/src/components/transactionsTable/TransactionRow.tsx b/packages/renderer/src/components/transactionsTable/TransactionRow.tsx index c3676bc5..20701d32 100644 --- a/packages/renderer/src/components/transactionsTable/TransactionRow.tsx +++ b/packages/renderer/src/components/transactionsTable/TransactionRow.tsx @@ -4,9 +4,21 @@ import type {BlacklistedAddress} from '../../types/Blacklist'; import TransactionIcon from './TransactionIcon'; import type {ITransactionRowData} from './TransactionsTypes'; import {useNetworkSettingsContext} from '/@/contexts/NetworkContext'; +import {formatUrl} from './TransactionsHelper'; const TransactionRow = ( - {timestamp, amount, sender, receiver, fee, memo, id, type}: ITransactionRowData, + { + timestamp, + amount, + sender, + receiver, + fee, + memo, + id, + type, + failed, + failure_reason, + }: ITransactionRowData, index: number, userAddress: string, blacklist: BlacklistedAddress[], @@ -53,14 +65,22 @@ const TransactionRow = ( > {' '} - {TransactionIcon(type, sender, receiver, userAddress, isScam)}{' '} + {TransactionIcon( + type, + sender, + receiver, + userAddress, + isScam, + failed, + failure_reason, + )}{' '} openLinkOnBrowser(`${settings?.explorerUrl}${urlPath}/${id}`)} + onClick={() => !isMempool && openLinkOnBrowser(formatUrl(id, settings?.explorerUrl))} target="_blank" rel="noreferrer" className="purple-text" diff --git a/packages/renderer/src/components/transactionsTable/TransactionsHelper.ts b/packages/renderer/src/components/transactionsTable/TransactionsHelper.ts index 0b23755e..d5686c61 100644 --- a/packages/renderer/src/components/transactionsTable/TransactionsHelper.ts +++ b/packages/renderer/src/components/transactionsTable/TransactionsHelper.ts @@ -14,7 +14,7 @@ export const mempoolQueryRowToTableRow = (mempoolRow: IMempoolQueryData) => { const isSelf = receiver === sender; const fee = 'Fee : ' + (mempoolRow.fee ? +toMINA(+mempoolRow.fee) : 0) + ' Mina'; const memo = mempoolRow.memo; - const type = ''; + const type = mempoolRow.kind === 'STAKE_DELEGATION' ? 'delegation' : 'payment'; return { id, amount, @@ -33,15 +33,16 @@ export const mempoolQueryRowToTableRow = (mempoolRow: IMempoolQueryData) => { * @returns ITransactionRowData */ export const transactionQueryRowToTableRow = (transactionRow: ITransactionQueryData) => { - const {timestamp} = transactionRow; + const {timestamp, status, failure_reason} = transactionRow; const id = transactionRow.hash; const amount = transactionRow.amount ? toMINA(transactionRow.amount) : 0; const sender = transactionRow.sender_public_key; const receiver = transactionRow.receiver_public_key; const fee = 'Fee : ' + (transactionRow.fee ? +toMINA(transactionRow.fee) : 0) + ' Mina'; - const type = transactionRow.type; + const type = transactionRow.command_type; const isSelf = receiver === sender; const memo = transactionRow.memo; + const failed = status === 'failed'; return { id, amount, @@ -52,5 +53,16 @@ export const transactionQueryRowToTableRow = (transactionRow: ITransactionQueryD memo, timestamp, type, + status, + failure_reason, + failed, }; }; + +export const formatUrl = (txId: string,url?: string) => { + if (url.includes('minascan.io')) { + return `${url}tx/${txId}`; + } else { + return `${url}transaction/${txId}`; + } +}; diff --git a/packages/renderer/src/components/transactionsTable/TransactionsTypes.ts b/packages/renderer/src/components/transactionsTable/TransactionsTypes.ts index cdc03820..82fe1545 100644 --- a/packages/renderer/src/components/transactionsTable/TransactionsTypes.ts +++ b/packages/renderer/src/components/transactionsTable/TransactionsTypes.ts @@ -7,6 +7,9 @@ export interface ITransactionQueryData { amount: string; hash: string; timestamp: string; + status: string; + command_type: string; + failure_reason: string; } export interface IMempoolQueryData { @@ -20,6 +23,7 @@ export interface IMempoolQueryData { fee?: number; memo: string; id: string; + kind: string; } export interface ITransactionTableProps { @@ -45,6 +49,9 @@ export interface ITransactionRowData { memo: string; type: string; timestamp?: number; + status: string; + failure_reason?: string; + failed: boolean; } export interface ITransactionQueryResult { diff --git a/packages/renderer/src/components/transactionsTable/WalletCreationTransaction.tsx b/packages/renderer/src/components/transactionsTable/WalletCreationTransaction.tsx index 1e0a8d9e..ef829d49 100644 --- a/packages/renderer/src/components/transactionsTable/WalletCreationTransaction.tsx +++ b/packages/renderer/src/components/transactionsTable/WalletCreationTransaction.tsx @@ -1,16 +1,14 @@ -import { ChevronsUp } from 'react-feather'; +import {ChevronsUp} from 'react-feather'; const WalletCreationTransaction = (index: number) => { return ( - - {' '} - - - + 1 Mina wallet creation fee - {/* 1 Mina */} ); }; diff --git a/packages/renderer/src/pages/Login.tsx b/packages/renderer/src/pages/Login.tsx index 16dcbdda..2df14cf3 100644 --- a/packages/renderer/src/pages/Login.tsx +++ b/packages/renderer/src/pages/Login.tsx @@ -1,6 +1,6 @@ import {Link, useNavigate} from 'react-router-dom'; import {useState, useEffect, useCallback} from 'react'; -import {useQuery} from '@apollo/client'; +import {useLazyQuery, useQuery} from '@apollo/client'; import {toast} from 'react-toastify'; import {ArrowLeft, ArrowRight} from 'react-feather'; import {deriveAccount, setPassphrase, spellMnemonic, storeAccounts, storeSession} from '/@/tools'; @@ -26,15 +26,10 @@ function Login({toggleLoader}: IProps) { const [showPasswordModal, setShowPasswordModal] = useState(false); const [passphraseError, setPassphraseError] = useState(null); const navigate = useNavigate(); - const { - data: userIdData, - error: userIdError, - loading: userIdLoading, - refetch: userIdRefetch, - } = useQuery(GET_ID, { - variables: {publicKey}, - skip: !publicKey, - }); + const [userIdFetch, {data: userIdData, error: userIdError, loading: userIdLoading}] = + useLazyQuery(GET_ID, { + variables: {publicKey}, + }); const setConfig = useSetRecoilState(configState); const {encryptData} = useSecureStorage(); @@ -62,11 +57,15 @@ function Login({toggleLoader}: IProps) { }; }, []); - const saveAndStoreSession = () => { + const saveAndStoreSession = (userId?: string | number, derivedPublicKey?: string) => { if (publicKey && publicKey !== '' && !userIdLoading && userIdData) { toggleLoader(true); const id = +userIdData?.idByPublicKey.id || -1; - storeSessionAndRedirect(publicKey, id); + storeSessionAndRedirect(derivedPublicKey || publicKey, id); + } else if (derivedPublicKey) { + toggleLoader(true); + const id = +userId || -1; + storeSessionAndRedirect(derivedPublicKey || publicKey, id); } }; @@ -85,7 +84,7 @@ function Login({toggleLoader}: IProps) { }, [userIdError]); const storeSessionAndRedirect = async (publicKey: string, id: number) => { - await userIdRefetch({publicKey}); + await userIdFetch({variables: {publicKey}}); const isUsingMnemonic = privateKey.trim().split(' ').length === 12; if (storePassphrase) { setPassphrase(isUsingMnemonic); @@ -161,11 +160,11 @@ function Login({toggleLoader}: IProps) { const derivedAccount = await deriveAccount(privateKey.trim()); if (derivedAccount.publicKey) { setPublicKey(derivedAccount.publicKey); - await userIdRefetch({publicKey: derivedAccount.publicKey}); + const {data} = await userIdFetch({variables: {publicKey: derivedAccount.publicKey}}); if (storePassphrase) { setShowPasswordModal(true); } else { - saveAndStoreSession(); + saveAndStoreSession(data?.idByPublicKey?.id, derivedAccount.publicKey); } } } catch (e) { diff --git a/packages/renderer/src/pages/Overview.tsx b/packages/renderer/src/pages/Overview.tsx index 8a97b0fd..e328f99c 100644 --- a/packages/renderer/src/pages/Overview.tsx +++ b/packages/renderer/src/pages/Overview.tsx @@ -131,11 +131,17 @@ const Overview = ({sessionData}: IProps) => { }; useEffect(() => { - if (transactionsData && mempoolData) { + if (transactionsData) { setLoading(false); } }, [transactionsData, mempoolData]); + useEffect(() => { + if (transactionsError) { + setLoading(false); + } + }, [transactionsError]); + return (
diff --git a/packages/renderer/src/pages/ZkApps.tsx b/packages/renderer/src/pages/ZkApps.tsx index 35d570ed..65c67fbb 100644 --- a/packages/renderer/src/pages/ZkApps.tsx +++ b/packages/renderer/src/pages/ZkApps.tsx @@ -1,11 +1,13 @@ import {ZkappSidebar} from '../components/UI/modals/zkAppIntegration/ZkappSidebar'; import {ZkappIframe} from '../components/UI/modals/zkAppIntegration/ZkappIframe'; +import { ZkappConnectedApps } from '../components/UI/modals/zkAppIntegration/ZkappConnectedApps'; const ZkApps = () => { return (
- - + {/* + */} +
); }; diff --git a/packages/renderer/src/pages/sendTX/SendTX.tsx b/packages/renderer/src/pages/sendTX/SendTX.tsx index 2b59e44f..df75fde7 100644 --- a/packages/renderer/src/pages/sendTX/SendTX.tsx +++ b/packages/renderer/src/pages/sendTX/SendTX.tsx @@ -68,12 +68,12 @@ function SendTX(props: IProps) { const [transactionData, setTransactionData] = useState(initialTransactionData); const [ledgerTransactionData, setLedgerTransactionData] = useState(''); const [storedPassphrase, setStoredPassphrase] = useState(''); - const {isLedgerEnabled} = useContext>(LedgerContext); const {getBalance, setShouldBalanceUpdate} = useContext>(BalanceContext); // const {wallet} = useWallet(); const wallet = useRecoilValue(walletState); const senderAddress = wallet.address; const balance = getBalance && getBalance(wallet.address); + const isLedgerEnabled = wallet.ledger; const { data: nonceData, refetch: nonceRefetch, diff --git a/packages/renderer/src/pages/stake/Stake.tsx b/packages/renderer/src/pages/stake/Stake.tsx index 5bb16cdd..4e2d7885 100644 --- a/packages/renderer/src/pages/stake/Stake.tsx +++ b/packages/renderer/src/pages/stake/Stake.tsx @@ -70,9 +70,9 @@ const Stake = ({sessionData}: IProps) => { const [selectedFee, setSelectedFee] = useState(0); const [ledgerTransactionData, setLedgerTransactionData] = useState(''); const [sendTransactionFlag, setSendTransactionFlag] = useState(false); - const {isLedgerEnabled} = useContext>(LedgerContext); + // const {isLedgerEnabled} = useContext>(LedgerContext); const {getBalance, setShouldBalanceUpdate} = useContext>(BalanceContext); - const {address, accountNumber} = useRecoilValue(walletState); + const {address, accountNumber,ledger:isLedgerEnabled} = useRecoilValue(walletState); const balance = getBalance && getBalance(address); const { diff --git a/packages/renderer/src/styles/__misc.scss b/packages/renderer/src/styles/__misc.scss index d0f64320..a1084c14 100644 --- a/packages/renderer/src/styles/__misc.scss +++ b/packages/renderer/src/styles/__misc.scss @@ -900,11 +900,12 @@ tbody { #draggable-bar { width: 100vw; - min-height: 2vh; - position: none; + min-height: 4vh; + position: absolute; left: 0px; top: 0; -webkit-app-region: drag; + z-index: 9999; } .huge-text { diff --git a/packages/renderer/src/styles/__zkapps.scss b/packages/renderer/src/styles/__zkapps.scss new file mode 100644 index 00000000..bdc15be3 --- /dev/null +++ b/packages/renderer/src/styles/__zkapps.scss @@ -0,0 +1,43 @@ +.zkapps-list-container { + gap: 40px; + margin-top: 50px; + flex-wrap: wrap; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); +} + +.zkapp-favicon { + width: 30px; + position: relative; + top: -20px; +} + +.zkapp-input { + max-width: 1200px; + margin-top: 20px; + display: flex; + flex-direction: row; + gap: 20px; +} + +.zkapp-open-button { + height: 45px; + max-width: 250px; +} + +.zkapp-warning-alert { + display: flex; + justify-content: start; + align-items: center; + gap: 20px; +} + +.zkapp-list-item { + transition: all 0.3s; + min-width: 350px; + margin: 0 auto; +} + +.zkapp-list-item:hover { + margin-top: -5px; +}