Update wallets

This commit is contained in:
AlphaX-Qortal
2025-03-12 08:18:27 +01:00
parent 8d31ed330b
commit 0e19fd1a7a
11 changed files with 4267 additions and 231 deletions

View File

@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" type="image/png" href="/ico.png" /> <link rel="icon" type="image/png" href="/qw-ico.png" />
<meta name="viewport" content="initial-scale=1, width=device-width" /> <meta name="viewport" content="initial-scale=1, width=device-width" />
<title>Qortal Wallets App</title> <title>Qortal Wallets App</title>
</head> </head>

26
package-lock.json generated
View File

@@ -22,7 +22,8 @@
"react-number-format": "^5.4.3", "react-number-format": "^5.4.3",
"react-qr-code": "^2.0.15", "react-qr-code": "^2.0.15",
"react-router": "^7.2.0", "react-router": "^7.2.0",
"react-router-dom": "^7.2.0" "react-router-dom": "^7.2.0",
"react-window": "^1.8.11"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^19.0.10", "@types/react": "^19.0.10",
@@ -2384,6 +2385,12 @@
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"node_modules/memoize-one": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
"license": "MIT"
},
"node_modules/merge-stream": { "node_modules/merge-stream": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -2767,6 +2774,23 @@
"react-dom": ">=16.6.0" "react-dom": ">=16.6.0"
} }
}, },
"node_modules/react-window": {
"version": "1.8.11",
"resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.11.tgz",
"integrity": "sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.0.0",
"memoize-one": ">=3.1.1 <6"
},
"engines": {
"node": ">8.0.0"
},
"peerDependencies": {
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/regenerator-runtime": { "node_modules/regenerator-runtime": {
"version": "0.14.1", "version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",

View File

@@ -23,7 +23,8 @@
"react-number-format": "^5.4.3", "react-number-format": "^5.4.3",
"react-qr-code": "^2.0.15", "react-qr-code": "^2.0.15",
"react-router": "^7.2.0", "react-router": "^7.2.0",
"react-router-dom": "^7.2.0" "react-router-dom": "^7.2.0",
"react-window": "^1.8.11"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^19.0.10", "@types/react": "^19.0.10",
@@ -32,4 +33,4 @@
"typescript": "^5.7.3", "typescript": "^5.7.3",
"vite": "^6.2.0" "vite": "^6.2.0"
} }
} }

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,10 +1,11 @@
import * as React from 'react'; import * as React from 'react';
import packageJson from '../package.json';
import { Container, Typography } from "@mui/material"; import { Container, Typography } from "@mui/material";
import { createTheme } from '@mui/material/styles'; import { createTheme } from '@mui/material/styles';
import { Session, Navigation } from '@toolpad/core/AppProvider'; import { Session, Navigation } from '@toolpad/core/AppProvider';
import { ReactRouterAppProvider } from '@toolpad/core/react-router'; import { ReactRouterAppProvider } from '@toolpad/core/react-router';
import { Route, Routes } from "react-router-dom"; import { Route, Routes } from "react-router-dom";
import { DashboardLayout } from '@toolpad/core/DashboardLayout'; import { DashboardLayout, type SidebarFooterProps } from '@toolpad/core/DashboardLayout';
import WalletContext, { IContextProps, UserNameAvatar } from './contexts/walletContext'; import WalletContext, { IContextProps, UserNameAvatar } from './contexts/walletContext';
import qort from "./assets/qort.png"; import qort from "./assets/qort.png";
import btc from "./assets/btc.png"; import btc from "./assets/btc.png";
@@ -223,24 +224,37 @@ function App() {
}, },
Pirate, Pirate,
]; ];
function SidebarFooter({ mini }: SidebarFooterProps) {
return (
<Typography
variant="caption"
sx={{ m: 1, whiteSpace: 'nowrap', overflow: 'hidden' }}
>
{mini ? `v${packageJson.version}` : `© ${new Date().getFullYear()} Qortal Wallets App v${packageJson.version}`}
</Typography>
);
}
const BRANDING = {
logo: <img src={qwallets} alt="MWA Logo" />,
title: <Typography>
<span style={{ color: '#60d0fd', fontSize: "24px", fontWeight: 700 }}>QORTAL </span>
<span style={{ color: '#05a2e4', fontSize: "24px", fontWeight: 700 }}>WALLETS </span>
<span style={{ color: '#02648d', fontSize: "24px", fontWeight: 700 }}>APP</span>
</Typography>
}
return ( return (
<ReactRouterAppProvider <ReactRouterAppProvider
session={session} session={session}
authentication={authentication} authentication={authentication}
navigation={NAVIGATION} navigation={NAVIGATION}
branding={{ branding={BRANDING}
logo: <img src={qwallets} alt="MWA Logo" />,
title: (<Typography>
<span style={{ color: '#60d0fd', fontSize: "24px", fontWeight: 700 }}>QORTAL </span>
<span style={{ color: '#05a2e4', fontSize: "24px", fontWeight: 700 }}>WALLETS </span>
<span style={{ color: '#02648d', fontSize: "24px", fontWeight: 700 }}>APP</span>
</Typography>)
}}
theme={walletTheme} theme={walletTheme}
> >
<WalletContext.Provider value={walletContextValue}> <WalletContext.Provider value={walletContextValue}>
<DashboardLayout defaultSidebarCollapsed> <DashboardLayout defaultSidebarCollapsed slots={{ sidebarFooter: SidebarFooter }}>
<Container sx={{ maxWidth: '100%' }} maxWidth={false}> <Container sx={{ maxWidth: '100%' }} maxWidth={false}>
<Routes> <Routes>
<Route path="/" element={<WelcomePage />} /> <Route path="/" element={<WelcomePage />} />

6
src/global.d.ts vendored
View File

@@ -29,7 +29,9 @@ interface QortalRequestOptions {
tag5?: string; tag5?: string;
coin?: string; coin?: string;
destinationAddress?: string; destinationAddress?: string;
amount?: number; amount?: number | Number;
recipient?: string;
fee?: number | any;
blob?: Blob; blob?: Blob;
mimeType?: string; mimeType?: string;
file?: File; file?: File;
@@ -40,6 +42,8 @@ interface QortalRequestOptions {
exactMatchNames?: boolean; exactMatchNames?: boolean;
creationBytes?: string; creationBytes?: string;
type?: string; type?: string;
host?: string;
port?: number;
assetId?: number; assetId?: number;
txType?: TransactionType[]; txType?: TransactionType[];
confirmationStatus?: string; confirmationStatus?: string;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -15,8 +15,10 @@ import {
DialogContent, DialogContent,
DialogTitle, DialogTitle,
IconButton, IconButton,
List,
ListItemButton,
ListItemText,
Paper, Paper,
Slide,
Slider, Slider,
Table, Table,
TableBody, TableBody,
@@ -34,6 +36,8 @@ import {
} from '@mui/material'; } from '@mui/material';
import { NumericFormat } from 'react-number-format'; import { NumericFormat } from 'react-number-format';
import TableCell, { tableCellClasses } from '@mui/material/TableCell'; import TableCell, { tableCellClasses } from '@mui/material/TableCell';
import Snackbar, { SnackbarCloseReason } from '@mui/material/Snackbar';
import Slide, { SlideProps } from '@mui/material/Slide';
import { TransitionProps } from '@mui/material/transitions'; import { TransitionProps } from '@mui/material/transitions';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import LinearProgress from '@mui/material/LinearProgress'; import LinearProgress from '@mui/material/LinearProgress';
@@ -46,7 +50,9 @@ import {
LastPage, LastPage,
CopyAllTwoTone, CopyAllTwoTone,
Close, Close,
Send Send,
Refresh,
PublishedWithChangesTwoTone
} from '@mui/icons-material'; } from '@mui/icons-material';
import coinLogoDOGE from '../../assets/doge.png'; import coinLogoDOGE from '../../assets/doge.png';
@@ -125,6 +131,10 @@ const Transition = React.forwardRef(function Transition(
return <Slide direction="up" ref={ref} {...props} />; return <Slide direction="up" ref={ref} {...props} />;
}); });
function SlideTransition(props: SlideProps) {
return <Slide {...props} direction="up" />;
}
const DogeQrDialog = styled(Dialog)(({ theme }) => ({ const DogeQrDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialogContent-root': { '& .MuiDialogContent-root': {
padding: theme.spacing(2), padding: theme.spacing(2),
@@ -132,6 +142,33 @@ const DogeQrDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialogActions-root': { '& .MuiDialogActions-root': {
padding: theme.spacing(1), padding: theme.spacing(1),
}, },
"& .MuiDialog-paper": {
borderRadius: "15px",
},
}));
const DogeElectrumDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialogContent-root': {
padding: theme.spacing(2),
},
'& .MuiDialogActions-root': {
padding: theme.spacing(1),
},
"& .MuiDialog-paper": {
borderRadius: "15px",
},
}));
const DogeSubmittDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialogContent-root': {
padding: theme.spacing(2),
},
'& .MuiDialogActions-root': {
padding: theme.spacing(1),
},
"& .MuiDialog-paper": {
borderRadius: "15px",
},
})); }));
const CustomWidthTooltip = styled(({ className, ...props }: TooltipProps) => ( const CustomWidthTooltip = styled(({ className, ...props }: TooltipProps) => (
@@ -223,6 +260,8 @@ export default function DogecoinWallet() {
const [walletInfoDoge, setWalletInfoDoge] = React.useState<any>({}); const [walletInfoDoge, setWalletInfoDoge] = React.useState<any>({});
const [walletBalanceDoge, setWalletBalanceDoge] = React.useState<any>(null); const [walletBalanceDoge, setWalletBalanceDoge] = React.useState<any>(null);
const [isLoadingWalletBalanceDoge, setIsLoadingWalletBalanceDoge] = React.useState<boolean>(true); const [isLoadingWalletBalanceDoge, setIsLoadingWalletBalanceDoge] = React.useState<boolean>(true);
const [allElectrumServersDoge, setAllElectrumServersDoge] = React.useState<any>([]);
const [currentElectrumServerDoge, setCurrentElectrumServerDoge] = React.useState<any>([]);
const [allWalletAddressesDoge, setAllWalletAddressesDoge] = React.useState<any>([]); const [allWalletAddressesDoge, setAllWalletAddressesDoge] = React.useState<any>([]);
const [transactionsDoge, setTransactionsDoge] = React.useState<any>([]); const [transactionsDoge, setTransactionsDoge] = React.useState<any>([]);
const [isLoadingDogeTransactions, setIsLoadingDogeTransactions] = React.useState<boolean>(true); const [isLoadingDogeTransactions, setIsLoadingDogeTransactions] = React.useState<boolean>(true);
@@ -231,9 +270,15 @@ export default function DogecoinWallet() {
const [copyDogeAddress, setCopyDogeAddress] = React.useState(''); const [copyDogeAddress, setCopyDogeAddress] = React.useState('');
const [copyDogeTxHash, setCopyDogeTxHash] = React.useState(''); const [copyDogeTxHash, setCopyDogeTxHash] = React.useState('');
const [openDogeQR, setOpenDogeQR] = React.useState(false); const [openDogeQR, setOpenDogeQR] = React.useState(false);
const [openDogeElectrum, setOpenDogeElectrum] = React.useState(false);
const [openDogeSend, setOpenDogeSend] = React.useState(false); const [openDogeSend, setOpenDogeSend] = React.useState(false);
const [amountDoge, setAmountDoge] = React.useState<number>(0); const [dogeAmount, setDogeAmount] = React.useState<number>(0);
const [feeDoge, setFeeDoge] = React.useState<number>(0); const [dogeRecipient, setDogeRecipient] = React.useState('');
const [dogeFee, setDogeFee] = React.useState<number>(0);
const [loadingRefreshDoge, setLoadingRefreshDoge] = React.useState(false);
const [openTxDogeSubmit, setOpenTxDogeSubmit] = React.useState(false);
const [openSendDogeSuccess, setOpenSendDogeSuccess] = React.useState(false);
const [openSendDogeeError, setOpenSendDogeError] = React.useState(false);
const emptyRows = page > 0 ? Math.max(0, (1 + page) * rowsPerPage - transactionsDoge.length) : 0; const emptyRows = page > 0 ? Math.max(0, (1 + page) * rowsPerPage - transactionsDoge.length) : 0;
@@ -245,15 +290,30 @@ export default function DogecoinWallet() {
setOpenDogeQR(false); setOpenDogeQR(false);
} }
const handleCloseDogeElectrum = () => {
setOpenDogeElectrum(false);
}
const handleOpenDogeSend = () => { const handleOpenDogeSend = () => {
setAmountDoge(0); setDogeAmount(0);
setFeeDoge(1000); setDogeRecipient('');
setDogeFee(1000);
setOpenDogeSend(true); setOpenDogeSend(true);
} }
const validateCanSendDoge = () => {
if (dogeAmount <= 0 || null || !dogeAmount) {
return true;
}
if (dogeRecipient.length < 34 || '') {
return true;
}
return false;
}
const handleCloseDogeSend = () => { const handleCloseDogeSend = () => {
setAmountDoge(0); setDogeAmount(0);
setFeeDoge(0); setDogeFee(0);
setOpenDogeSend(false); setOpenDogeSend(false);
} }
@@ -269,7 +329,7 @@ export default function DogecoinWallet() {
setCopyDogeTxHash(''); setCopyDogeTxHash('');
} }
const handleChangePage = (event: React.MouseEvent<HTMLButtonElement> | null, newPage: number,) => { const handleChangePage = (_event: React.MouseEvent<HTMLButtonElement> | null, newPage: number,) => {
setPage(newPage); setPage(newPage);
}; };
@@ -278,13 +338,29 @@ export default function DogecoinWallet() {
setPage(0); setPage(0);
}; };
const handleChangeDogeAmount = (_: Event, newValue: number | number[]) => { const handleChangeDogeFee = (_: Event, newValue: number | number[]) => {
setAmountDoge(newValue as number); setDogeFee(newValue as number);
console.log("AMOUNT DOGE", amountDoge) setDogeAmount(0);
}; };
const handleChangeDogeFee = (_: Event, newValue: number | number[]) => { const handleCloseSendDogeSuccess = (
setFeeDoge(newValue as number); _event?: React.SyntheticEvent | Event,
reason?: SnackbarCloseReason,
) => {
if (reason === 'clickaway') {
return;
}
setOpenSendDogeSuccess(false);
};
const handleCloseSendDogeError = (
_event?: React.SyntheticEvent | Event,
reason?: SnackbarCloseReason,
) => {
if (reason === 'clickaway') {
return;
}
setOpenSendDogeError(false);
}; };
const getWalletInfoDoge = async () => { const getWalletInfoDoge = async () => {
@@ -337,6 +413,37 @@ export default function DogecoinWallet() {
} }
}, [isAuthenticated]); }, [isAuthenticated]);
const getElectrumServersDoge = async () => {
try {
const response = await qortalRequest({
action: "GET_CROSSCHAIN_SERVER_INFO",
coin: "DOGE"
});
if (!response?.error) {
setAllElectrumServersDoge(response);
let currentDogeServer = response.filter(function (item: { isCurrent: boolean; }) {
return item.isCurrent === true;
});
setCurrentElectrumServerDoge(currentDogeServer);
console.log("GET DOGE SERVERS INFO", response);
console.log("CURRENT DOGE SERVER INFO", currentDogeServer);
}
} catch (error) {
setAllElectrumServersDoge({});
console.log("ERROR GET DOGE SERVERS INFO", error);
}
}
React.useEffect(() => {
if (!isAuthenticated) return;
getElectrumServersDoge();
}, [isAuthenticated]);
const handleOpenDogeElectrum = async () => {
await getElectrumServersDoge();
setOpenDogeElectrum(true);
}
const getTransactionsDoge = async () => { const getTransactionsDoge = async () => {
try { try {
setIsLoadingDogeTransactions(true); setIsLoadingDogeTransactions(true);
@@ -376,6 +483,21 @@ export default function DogecoinWallet() {
getTransactionsDoge(); getTransactionsDoge();
}, [isAuthenticated]); }, [isAuthenticated]);
const handleLoadingRefreshDoge = async () => {
setLoadingRefreshDoge(true);
await getTransactionsDoge();
setLoadingRefreshDoge(false);
}
const handleSendMaxDoge = () => {
const maxDogeAmount = parseFloat((walletBalanceDoge - ((dogeFee * 1000) / 1e8)).toFixed(8));
if (maxDogeAmount <= 0) {
setDogeAmount(0);
} else {
setDogeAmount(maxDogeAmount);
}
}
const DogeQrDialogPage = () => { const DogeQrDialogPage = () => {
return ( return (
<DogeQrDialog <DogeQrDialog
@@ -407,6 +529,44 @@ export default function DogecoinWallet() {
); );
} }
const sendDogeRequest = async () => {
setOpenTxDogeSubmit(true);
const dogeFeeCalculated = Number(dogeFee / 1e8).toFixed(8);
console.log("RECIPIENT", dogeRecipient);
console.log("AMOUNT", dogeAmount);
console.log("FEE", dogeFeeCalculated);
try {
const sendRequest = await qortalRequest({
action: "SEND_COIN",
coin: "DOGE",
recipient: dogeRecipient,
amount: dogeAmount,
fee: dogeFeeCalculated
});
if (!sendRequest?.error) {
setDogeAmount(0);
setDogeRecipient('');
setDogeFee(1000);
setOpenTxDogeSubmit(false);
setOpenSendDogeSuccess(true);
setIsLoadingWalletBalanceDoge(true);
await timeoutDelay(3000);
getWalletBalanceDoge();
console.log("DOGE SENDED", sendRequest);
}
} catch (error) {
setDogeAmount(0);
setDogeRecipient('');
setDogeFee(1000);
setOpenTxDogeSubmit(false);
setOpenSendDogeError(true);
setIsLoadingWalletBalanceDoge(true);
await timeoutDelay(3000);
getWalletBalanceDoge();
console.log("ERROR SENDING DOGE", error);
}
}
const DogeSendDialogPage = () => { const DogeSendDialogPage = () => {
return ( return (
<Dialog <Dialog
@@ -415,6 +575,58 @@ export default function DogecoinWallet() {
onClose={handleCloseDogeSend} onClose={handleCloseDogeSend}
slots={{ transition: Transition }} slots={{ transition: Transition }}
> >
<DogeSubmittDialog
fullWidth={true}
maxWidth='xs'
open={openTxDogeSubmit}
>
<DialogContent>
<Box sx={{ display: 'flex', justifyContent: 'center', flexWrap: 'wrap' }}>
<div style={{
width: "100%",
display: 'flex',
justifyContent: 'center'
}}>
<CircularProgress color="success" size={64} />
</div>
<div style={{
width: "100%",
display: 'flex',
justifyContent: 'center',
marginTop: '20px'
}}>
<Typography variant="h6" sx={{ color: 'primary.main', fontStyle: 'italic', fontWeight: 700 }}>
Processing Transaction Please Wait...
</Typography>
</div>
</Box>
</DialogContent>
</DogeSubmittDialog>
<Snackbar
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
open={openSendDogeSuccess}
autoHideDuration={4000}
slots={{ transition: SlideTransition }}
onClose={handleCloseSendDogeSuccess}>
<Alert
onClose={handleCloseSendDogeSuccess}
severity="success"
variant="filled"
sx={{ width: '100%' }}
>
Sent DOGE transaction was successful!
</Alert>
</Snackbar>
<Snackbar open={openSendDogeeError} autoHideDuration={4000} onClose={handleCloseSendDogeError}>
<Alert
onClose={handleCloseSendDogeError}
severity="error"
variant="filled"
sx={{ width: '100%' }}
>
Something went wrong, please try again!
</Alert>
</Snackbar>
<AppBar sx={{ position: 'static' }}> <AppBar sx={{ position: 'static' }}>
<Toolbar> <Toolbar>
<IconButton <IconButton
@@ -437,10 +649,11 @@ export default function DogecoinWallet() {
Transfer DOGE Transfer DOGE
</Typography> </Typography>
<Button <Button
disabled={validateCanSendDoge()}
variant="contained" variant="contained"
startIcon={<Send />} startIcon={<Send />}
aria-label="send" aria-label="send-doge"
onClick={handleOpenDogeSend} onClick={sendDogeRequest}
sx={{ backgroundColor: "#05a2e4", color: "white", "&:hover": { backgroundColor: "#02648d", } }} sx={{ backgroundColor: "#05a2e4", color: "white", "&:hover": { backgroundColor: "#02648d", } }}
> >
SEND SEND
@@ -468,7 +681,7 @@ export default function DogecoinWallet() {
gutterBottom gutterBottom
sx={{ color: 'text.primary', fontWeight: 700 }} sx={{ color: 'text.primary', fontWeight: 700 }}
> >
{walletBalanceDoge + " DOGE"} {isLoadingWalletBalanceDoge ? <Box sx={{ width: '175px' }}><LinearProgress /></Box> : walletBalanceDoge + " DOGE"}
</Typography> </Typography>
</div> </div>
<div style={{ <div style={{
@@ -481,7 +694,6 @@ export default function DogecoinWallet() {
<Typography <Typography
variant="h5" variant="h5"
align="center" align="center"
gutterBottom
sx={{ color: 'primary.main', fontWeight: 700 }} sx={{ color: 'primary.main', fontWeight: 700 }}
> >
Max Sendable:&nbsp;&nbsp; Max Sendable:&nbsp;&nbsp;
@@ -489,11 +701,27 @@ export default function DogecoinWallet() {
<Typography <Typography
variant="h5" variant="h5"
align="center" align="center"
gutterBottom
sx={{ color: 'text.primary', fontWeight: 700 }} sx={{ color: 'text.primary', fontWeight: 700 }}
> >
{(walletBalanceDoge - 0.05000000) + " DOGE"} {(() => {
const newMaxDogeAmount = parseFloat((walletBalanceDoge - ((dogeFee * 1000) / 1e8)).toFixed(8));
if (newMaxDogeAmount < 0) {
return Number(0.00000000) + " DOGE"
} else {
return newMaxDogeAmount + " DOGE"
}
})()}
</Typography> </Typography>
<div style={{ marginInlineStart: '15px' }}>
<Button
variant="outlined"
size="small"
onClick={handleSendMaxDoge}
style={{ borderRadius: 50 }}
>
Send Max
</Button>
</div>
</div> </div>
<Box <Box
sx={{ sx={{
@@ -507,22 +735,32 @@ export default function DogecoinWallet() {
> >
<NumericFormat <NumericFormat
decimalScale={8} decimalScale={8}
value={amountDoge} defaultValue={0}
value={dogeAmount}
allowNegative={false} allowNegative={false}
customInput={TextField} customInput={TextField}
valueIsNumericString valueIsNumericString
variant="outlined" variant="outlined"
label="Amount (DOGE)" label="Amount (DOGE)"
isAllowed={(values) => {
const maxDogeCoin = (walletBalanceDoge - (dogeFee * 1000) / 1e8);
const { formattedValue, floatValue } = values;
return formattedValue === "" || floatValue <= maxDogeCoin;
}}
onValueChange={(values) => { onValueChange={(values) => {
setAmountDoge(values.floatValue); setDogeAmount(values.floatValue);
}} }}
required required
/> />
<TextField <TextField
required required
label="Receiver Adress" label="Receiver Adress"
id="dogeaddress" id="dogea-address"
margin="normal" margin="normal"
value={dogeRecipient}
helperText="Doge Address 34 Characters long !"
slotProps={{ htmlInput: { maxLength: 34, minLength: 34 } }}
onChange={(e) => setDogeRecipient(e.target.value)}
/> />
</Box> </Box>
<div style={{ <div style={{
@@ -540,7 +778,7 @@ export default function DogecoinWallet() {
width: '50ch' width: '50ch'
}}> }}>
<Typography id="doge-fee-slider" gutterBottom> <Typography id="doge-fee-slider" gutterBottom>
Current fee per byte : {feeDoge} SAT Current fee per byte : {dogeFee} SAT
</Typography> </Typography>
<Slider <Slider
track={false} track={false}
@@ -554,6 +792,12 @@ export default function DogecoinWallet() {
marks={dogeMarks} marks={dogeMarks}
onChange={handleChangeDogeFee} onChange={handleChangeDogeFee}
/> />
<Typography
align="center"
sx={{ fontWeight: 600, fontSize: '14px', marginTop: '15px' }}
>
Low fees may result in slow or unconfirmed transactions.
</Typography>
</Box> </Box>
</div> </div>
</Dialog> </Dialog>
@@ -602,8 +846,8 @@ export default function DogecoinWallet() {
? transactionsDoge.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) ? transactionsDoge.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
: transactionsDoge : transactionsDoge
).map((row: { ).map((row: {
inputs: { address: any; }[]; inputs: { address: any; addressInWallet: boolean; }[];
outputs: { address: any; }[]; outputs: { address: any; addressInWallet: boolean; }[];
txHash: string; txHash: string;
totalAmount: any; totalAmount: any;
timestamp: number; timestamp: number;
@@ -611,27 +855,31 @@ export default function DogecoinWallet() {
<StyledTableRow> <StyledTableRow>
<StyledTableCell style={{ width: 'auto' }} align="left"> <StyledTableCell style={{ width: 'auto' }} align="left">
{(() => { {(() => {
if (allWalletAddressesDoge.some(dogeAll => dogeAll.address === row?.inputs[0].address)) { if (row?.totalAmount < 0) {
return <div style={{ color: '#05a2e4' }}>{row?.inputs[0].address}</div>; let meWasSender = row?.outputs.filter(function (item: { addressInWallet: boolean; }) {
return item.addressInWallet === true;
});
return <div style={{ color: '#05a2e4' }}>{meWasSender[0]?.address}</div>;
} else { } else {
return row?.inputs[0].address; let meWasNotSender = row?.outputs.filter(function (item: { addressInWallet: boolean; }) {
return item.addressInWallet === false;
});
return meWasNotSender[0].address;
} }
})()} })()}
</StyledTableCell> </StyledTableCell>
<StyledTableCell style={{ width: 'auto' }} align="left"> <StyledTableCell style={{ width: 'auto' }} align="left">
{(() => { {(() => {
if (row?.outputs[0].address === row?.inputs[0].address) { if (row?.totalAmount < 0) {
if (allWalletAddressesDoge.some((dogeAll: { address: any; }) => dogeAll.address === row?.outputs[1].address)) { let meWasNotRecipient = row?.outputs.filter(function (item: { addressInWallet: boolean; }) {
return <div style={{ color: '#05a2e4' }}>{row?.outputs[1].address}</div>; return item.addressInWallet === false;
} else { });
return row?.outputs[1].address; return meWasNotRecipient[0].address;
} } else if (row?.totalAmount > 0) {
} else { let meWasRecipient = row?.outputs.filter(function (item: { addressInWallet: boolean; }) {
if (allWalletAddressesDoge.some((dogeAll: { address: any; }) => dogeAll.address === row?.outputs[0].address)) { return item.addressInWallet === true;
return <div style={{ color: '#05a2e4' }}>{row?.outputs[0].address}</div>; });
} else { return <div style={{ color: '#05a2e4' }}>{meWasRecipient[0]?.address}</div>
return row?.outputs[0].address;
}
} }
})()} })()}
</StyledTableCell> </StyledTableCell>
@@ -644,7 +892,7 @@ export default function DogecoinWallet() {
</CustomWidthTooltip> </CustomWidthTooltip>
</StyledTableCell> </StyledTableCell>
<StyledTableCell style={{ width: 'auto' }} align="left"> <StyledTableCell style={{ width: 'auto' }} align="left">
{row?.outputs[0].address === walletInfoDoge?.address ? {row?.totalAmount > 0 ?
<div style={{ color: '#66bb6a' }}>+{(Number(row?.totalAmount) / 1e8).toFixed(8)}</div> : <div style={{ color: '#f44336' }}>{(Number(row?.totalAmount) / 1e8).toFixed(8)}</div> <div style={{ color: '#66bb6a' }}>+{(Number(row?.totalAmount) / 1e8).toFixed(8)}</div> : <div style={{ color: '#f44336' }}>{(Number(row?.totalAmount) / 1e8).toFixed(8)}</div>
} }
</StyledTableCell> </StyledTableCell>
@@ -686,10 +934,77 @@ export default function DogecoinWallet() {
); );
} }
const setNewCurrentDogeServer = async (typeServer: string, hostServer: string, portServer: number) => {
console.log("SERVER CHHOSED", typeServer, hostServer, portServer);
try {
const setServer = await qortalRequest({
action: "SET_CURRENT_FOREIGN_SERVER",
coin: "DOGE",
type: typeServer,
host: hostServer,
port: portServer
});
if (!setServer?.error) {
await getElectrumServersDoge();
setOpenDogeElectrum(false);
await getWalletBalanceDoge();
await getTransactionsDoge();
}
} catch (error) {
await getElectrumServersDoge();
setOpenDogeElectrum(false);
console.log("ERROR GET DOGE SERVERS INFO", error);
}
}
const DogeElectrumDialogPage = () => {
return (
<DogeElectrumDialog
onClose={handleCloseDogeQR}
aria-labelledby="doge-electrum-servers"
open={openDogeElectrum}
keepMounted={false}
>
<DialogTitle sx={{ m: 0, p: 2, fontSize: '14px' }} id="doge-electrum-servers">
Available Dogecoin Electrum Servers.
</DialogTitle>
<DialogContent dividers>
<Box sx={{
width: '100%',
maxWidth: 500,
position: 'relative',
overflow: 'auto',
maxHeight: 400
}}>
<List>
{(
allElectrumServersDoge
).map((server: {
connectionType: string;
hostName: string;
port: number;
}) => (
<ListItemButton onClick={() => { setNewCurrentDogeServer(server?.connectionType, server?.hostName, server?.port) }}>
<ListItemText primary={server?.hostName + ':' + server?.port} />
</ListItemButton>
))}
</List>
</Box>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleCloseDogeElectrum}>
CLOSE
</Button>
</DialogActions>
</DogeElectrumDialog>
);
}
return ( return (
<Box sx={{ width: '100%', marginTop: "20px" }}> <Box sx={{ width: '100%', marginTop: "20px" }}>
{DogeSendDialogPage()} {DogeSendDialogPage()}
{DogeQrDialogPage()} {DogeQrDialogPage()}
{DogeElectrumDialogPage()}
<Typography gutterBottom variant="h5" sx={{ color: 'primary.main', fontStyle: 'italic', fontWeight: 700 }}> <Typography gutterBottom variant="h5" sx={{ color: 'primary.main', fontStyle: 'italic', fontWeight: 700 }}>
Dogecoin Wallet Dogecoin Wallet
</Typography> </Typography>
@@ -741,12 +1056,38 @@ export default function DogecoinWallet() {
> >
{walletInfoDoge?.address} {walletInfoDoge?.address}
</Typography> </Typography>
<Tooltip placement="top" title={copyDogeAddress ? copyDogeAddress : "Copy Address"}> <Tooltip placement="right" title={copyDogeAddress ? copyDogeAddress : "Copy Address"}>
<IconButton aria-label="copy" size="small" onClick={() => { navigator.clipboard.writeText(walletInfoDoge?.address), changeCopyDogeStatus() }}> <IconButton aria-label="copy" size="small" onClick={() => { navigator.clipboard.writeText(walletInfoDoge?.address), changeCopyDogeStatus() }}>
<CopyAllTwoTone fontSize="small" /> <CopyAllTwoTone fontSize="small" />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
</div> </div>
<div style={{
width: "100%",
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<Typography
variant="subtitle1"
align="center"
sx={{ color: 'primary.main', fontWeight: 700 }}
>
Electrum Server:&nbsp;&nbsp;
</Typography>
<Typography
variant="subtitle1"
align="center"
sx={{ color: 'text.primary', fontWeight: 700 }}
>
{currentElectrumServerDoge[0]?.hostName + ":" + currentElectrumServerDoge[0]?.port}
</Typography>
<Tooltip placement="right" title="CHange Server">
<IconButton aria-label="open-electrum" size="small" onClick={handleOpenDogeElectrum}>
<PublishedWithChangesTwoTone fontSize="small" />
</IconButton>
</Tooltip>
</div>
<div style={{ <div style={{
width: "100%", width: "100%",
display: 'flex', display: 'flex',
@@ -779,9 +1120,27 @@ export default function DogecoinWallet() {
Address Book Address Book
</WalletButtons> </WalletButtons>
</div> </div>
<Typography variant="h6" paddingTop={2} paddingBottom={2}> <div style={{
Transactions: width: "100%",
</Typography> display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}}>
<Typography variant="h6" paddingTop={2} paddingBottom={2}>
Transactions:
</Typography>
<Button
size="small"
onClick={handleLoadingRefreshDoge}
loading={loadingRefreshDoge}
loadingPosition="start"
startIcon={<Refresh />}
variant="outlined"
style={{ borderRadius: 50 }}
>
Refresh
</Button>
</div>
{isLoadingDogeTransactions ? tableLoader() : transactionsTable()} {isLoadingDogeTransactions ? tableLoader() : transactionsTable()}
</WalleteCard> </WalleteCard>
</Box> </Box>

View File

@@ -15,8 +15,10 @@ import {
DialogContent, DialogContent,
DialogTitle, DialogTitle,
IconButton, IconButton,
List,
ListItemButton,
ListItemText,
Paper, Paper,
Slide,
Slider, Slider,
Table, Table,
TableBody, TableBody,
@@ -34,6 +36,8 @@ import {
} from '@mui/material'; } from '@mui/material';
import { NumericFormat } from 'react-number-format'; import { NumericFormat } from 'react-number-format';
import TableCell, { tableCellClasses } from '@mui/material/TableCell'; import TableCell, { tableCellClasses } from '@mui/material/TableCell';
import Snackbar, { SnackbarCloseReason } from '@mui/material/Snackbar';
import Slide, { SlideProps } from '@mui/material/Slide';
import { TransitionProps } from '@mui/material/transitions'; import { TransitionProps } from '@mui/material/transitions';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import LinearProgress from '@mui/material/LinearProgress'; import LinearProgress from '@mui/material/LinearProgress';
@@ -46,7 +50,9 @@ import {
LastPage, LastPage,
CopyAllTwoTone, CopyAllTwoTone,
Close, Close,
Send Send,
Refresh,
PublishedWithChangesTwoTone
} from '@mui/icons-material'; } from '@mui/icons-material';
import coinLogoLTC from '../../assets/ltc.png'; import coinLogoLTC from '../../assets/ltc.png';
@@ -125,6 +131,10 @@ const Transition = React.forwardRef(function Transition(
return <Slide direction="up" ref={ref} {...props} />; return <Slide direction="up" ref={ref} {...props} />;
}); });
function SlideTransition(props: SlideProps) {
return <Slide {...props} direction="up" />;
}
const LtcQrDialog = styled(Dialog)(({ theme }) => ({ const LtcQrDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialogContent-root': { '& .MuiDialogContent-root': {
padding: theme.spacing(2), padding: theme.spacing(2),
@@ -132,6 +142,33 @@ const LtcQrDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialogActions-root': { '& .MuiDialogActions-root': {
padding: theme.spacing(1), padding: theme.spacing(1),
}, },
"& .MuiDialog-paper": {
borderRadius: "15px",
},
}));
const LtcElectrumDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialogContent-root': {
padding: theme.spacing(2),
},
'& .MuiDialogActions-root': {
padding: theme.spacing(1),
},
"& .MuiDialog-paper": {
borderRadius: "15px",
},
}));
const LtcSubmittDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialogContent-root': {
padding: theme.spacing(2),
},
'& .MuiDialogActions-root': {
padding: theme.spacing(1),
},
"& .MuiDialog-paper": {
borderRadius: "15px",
},
})); }));
const CustomWidthTooltip = styled(({ className, ...props }: TooltipProps) => ( const CustomWidthTooltip = styled(({ className, ...props }: TooltipProps) => (
@@ -212,7 +249,6 @@ export default function LitecoinWallet() {
const { isAuthenticated } = React.useContext(WalletContext); const { isAuthenticated } = React.useContext(WalletContext);
if (!isAuthenticated) { if (!isAuthenticated) {
console.log("WE ARE NOT LOGGED IN");
return ( return (
<Alert variant="filled" severity="error"> <Alert variant="filled" severity="error">
You must sign in, to use the Litecoin wallet !!! You must sign in, to use the Litecoin wallet !!!
@@ -223,6 +259,8 @@ export default function LitecoinWallet() {
const [walletInfoLtc, setWalletInfoLtc] = React.useState<any>({}); const [walletInfoLtc, setWalletInfoLtc] = React.useState<any>({});
const [walletBalanceLtc, setWalletBalanceLtc] = React.useState<any>(null); const [walletBalanceLtc, setWalletBalanceLtc] = React.useState<any>(null);
const [isLoadingWalletBalanceLtc, setIsLoadingWalletBalanceLtc] = React.useState<boolean>(true); const [isLoadingWalletBalanceLtc, setIsLoadingWalletBalanceLtc] = React.useState<boolean>(true);
const [allElectrumServersLtc, setAllElectrumServersLtc] = React.useState<any>([]);
const [currentElectrumServerLtc, setCurrentElectrumServerLtc] = React.useState<any>([]);
const [allWalletAddressesLtc, setAllWalletAddressesLtc] = React.useState<any>([]); const [allWalletAddressesLtc, setAllWalletAddressesLtc] = React.useState<any>([]);
const [transactionsLtc, setTransactionsLtc] = React.useState<any>([]); const [transactionsLtc, setTransactionsLtc] = React.useState<any>([]);
const [isLoadingLtcTransactions, setIsLoadingLtcTransactions] = React.useState<boolean>(true); const [isLoadingLtcTransactions, setIsLoadingLtcTransactions] = React.useState<boolean>(true);
@@ -231,9 +269,15 @@ export default function LitecoinWallet() {
const [copyLtcAddress, setCopyLtcAddress] = React.useState(''); const [copyLtcAddress, setCopyLtcAddress] = React.useState('');
const [copyLtcTxHash, setCopyLtcTxHash] = React.useState(''); const [copyLtcTxHash, setCopyLtcTxHash] = React.useState('');
const [openLtcQR, setOpenLtcQR] = React.useState(false); const [openLtcQR, setOpenLtcQR] = React.useState(false);
const [openLtcElectrum, setOpenLtcElectrum] = React.useState(false);
const [openLtcSend, setOpenLtcSend] = React.useState(false); const [openLtcSend, setOpenLtcSend] = React.useState(false);
const [amountLtc, setAmountLtc] = React.useState<number>(0); const [ltcAmount, setLtcAmount] = React.useState<number>(0);
const [feeLtc, setFeeLtc] = React.useState<number>(0); const [ltcRecipient, setLtcRecipient] = React.useState('');
const [ltcFee, setLtcFee] = React.useState<number>(0);
const [loadingRefreshLtc, setLoadingRefreshLtc] = React.useState(false);
const [openTxLtcSubmit, setOpenTxLtcSubmit] = React.useState(false);
const [openSendLtcSuccess, setOpenSendLtcSuccess] = React.useState(false);
const [openSendLtceError, setOpenSendLtcError] = React.useState(false);
const emptyRows = page > 0 ? Math.max(0, (1 + page) * rowsPerPage - transactionsLtc.length) : 0; const emptyRows = page > 0 ? Math.max(0, (1 + page) * rowsPerPage - transactionsLtc.length) : 0;
@@ -245,15 +289,30 @@ export default function LitecoinWallet() {
setOpenLtcQR(false); setOpenLtcQR(false);
} }
const handleCloseLtcElectrum = () => {
setOpenLtcElectrum(false);
}
const handleOpenLtcSend = () => { const handleOpenLtcSend = () => {
setAmountLtc(0); setLtcAmount(0);
setFeeLtc(30); setLtcRecipient('');
setLtcFee(30);
setOpenLtcSend(true); setOpenLtcSend(true);
} }
const validateCanSendLtc = () => {
if (ltcAmount <= 0 || null || !ltcAmount) {
return true;
}
if (ltcRecipient.length < 34 || '') {
return true;
}
return false;
}
const handleCloseLtcSend = () => { const handleCloseLtcSend = () => {
setAmountLtc(0); setLtcAmount(0);
setFeeLtc(0); setLtcFee(0);
setOpenLtcSend(false); setOpenLtcSend(false);
} }
@@ -269,7 +328,7 @@ export default function LitecoinWallet() {
setCopyLtcTxHash(''); setCopyLtcTxHash('');
} }
const handleChangePage = (event: React.MouseEvent<HTMLButtonElement> | null, newPage: number,) => { const handleChangePage = (_event: React.MouseEvent<HTMLButtonElement> | null, newPage: number,) => {
setPage(newPage); setPage(newPage);
}; };
@@ -278,12 +337,29 @@ export default function LitecoinWallet() {
setPage(0); setPage(0);
}; };
const handleChangeLtcAmount = (_: Event, newValue: number | number[]) => { const handleChangeLtcFee = (_: Event, newValue: number | number[]) => {
setAmountLtc(newValue as number); setLtcFee(newValue as number);
setLtcAmount(0);
}; };
const handleChangeLtcFee = (_: Event, newValue: number | number[]) => { const handleCloseSendLtcSuccess = (
setFeeLtc(newValue as number); _event?: React.SyntheticEvent | Event,
reason?: SnackbarCloseReason,
) => {
if (reason === 'clickaway') {
return;
}
setOpenSendLtcSuccess(false);
};
const handleCloseSendLtcError = (
_event?: React.SyntheticEvent | Event,
reason?: SnackbarCloseReason,
) => {
if (reason === 'clickaway') {
return;
}
setOpenSendLtcError(false);
}; };
const getWalletInfoLtc = async () => { const getWalletInfoLtc = async () => {
@@ -294,11 +370,10 @@ export default function LitecoinWallet() {
}); });
if (!response?.error) { if (!response?.error) {
setWalletInfoLtc(response); setWalletInfoLtc(response);
console.log("GET LTC WALLET INFO", response);
} }
} catch (error) { } catch (error) {
setWalletInfoLtc({}); setWalletInfoLtc({});
console.log("ERROR GET LTC WALLET INFO", error); console.error("ERROR GET LTC WALLET INFO", error);
} }
} }
@@ -316,12 +391,11 @@ export default function LitecoinWallet() {
if (!response?.error) { if (!response?.error) {
setWalletBalanceLtc(response); setWalletBalanceLtc(response);
setIsLoadingWalletBalanceLtc(false); setIsLoadingWalletBalanceLtc(false);
console.log("GET LTC BALANCE", response);
} }
} catch (error) { } catch (error) {
setWalletBalanceLtc(null); setWalletBalanceLtc(null);
setIsLoadingWalletBalanceLtc(false); setIsLoadingWalletBalanceLtc(false);
console.log("ERROR GET LTC BALANCE", error); console.error("ERROR GET LTC BALANCE", error);
} }
} }
@@ -336,6 +410,35 @@ export default function LitecoinWallet() {
} }
}, [isAuthenticated]); }, [isAuthenticated]);
const getElectrumServersLtc = async () => {
try {
const response = await qortalRequest({
action: "GET_CROSSCHAIN_SERVER_INFO",
coin: "LTC"
});
if (!response?.error) {
setAllElectrumServersLtc(response);
let currentLtcServer = response.filter(function (item: { isCurrent: boolean; }) {
return item.isCurrent === true;
});
setCurrentElectrumServerLtc(currentLtcServer);
}
} catch (error) {
setAllElectrumServersLtc({});
console.error("ERROR GET LTC SERVERS INFO", error);
}
}
React.useEffect(() => {
if (!isAuthenticated) return;
getElectrumServersLtc();
}, [isAuthenticated]);
const handleOpenLtcElectrum = async () => {
await getElectrumServersLtc();
setOpenLtcElectrum(true);
}
const getTransactionsLtc = async () => { const getTransactionsLtc = async () => {
try { try {
setIsLoadingLtcTransactions(true); setIsLoadingLtcTransactions(true);
@@ -351,22 +454,20 @@ export default function LitecoinWallet() {
await responseLtcAllAddresses; await responseLtcAllAddresses;
if (!responseLtcAllAddresses?.error) { if (!responseLtcAllAddresses?.error) {
setAllWalletAddressesLtc(responseLtcAllAddresses); setAllWalletAddressesLtc(responseLtcAllAddresses);
console.log("GET LTC ALL ADDRESSES", responseLtcAllAddresses);
} }
} catch (error) { } catch (error) {
setAllWalletAddressesLtc([]); setAllWalletAddressesLtc([]);
console.log("ERROR GET LTC ALL ADDRESSES", error); console.error("ERROR GET LTC ALL ADDRESSES", error);
} }
await responseLtcTransactions; await responseLtcTransactions;
if (!responseLtcTransactions?.error) { if (!responseLtcTransactions?.error) {
setTransactionsLtc(responseLtcTransactions); setTransactionsLtc(responseLtcTransactions);
setIsLoadingLtcTransactions(false); setIsLoadingLtcTransactions(false);
console.log("GET LTC TRANSACTIONS", responseLtcTransactions);
} }
} catch (error) { } catch (error) {
setIsLoadingLtcTransactions(false); setIsLoadingLtcTransactions(false);
setTransactionsLtc([]); setTransactionsLtc([]);
console.log("ERROR GET LTC TRANSACTIONS", error); console.error("ERROR GET LTC TRANSACTIONS", error);
} }
} }
@@ -375,6 +476,21 @@ export default function LitecoinWallet() {
getTransactionsLtc(); getTransactionsLtc();
}, [isAuthenticated]); }, [isAuthenticated]);
const handleLoadingRefreshLtc = async () => {
setLoadingRefreshLtc(true);
await getTransactionsLtc();
setLoadingRefreshLtc(false);
}
const handleSendMaxLtc = () => {
const maxLtcAmount = parseFloat((walletBalanceLtc - ((ltcFee * 1000) / 1e8)).toFixed(8));
if (maxLtcAmount <= 0) {
setLtcAmount(0);
} else {
setLtcAmount(maxLtcAmount);
}
}
const LtcQrDialogPage = () => { const LtcQrDialogPage = () => {
return ( return (
<LtcQrDialog <LtcQrDialog
@@ -406,6 +522,40 @@ export default function LitecoinWallet() {
); );
} }
const sendLtcRequest = async () => {
setOpenTxLtcSubmit(true);
const ltcFeeCalculated = Number(ltcFee / 1e8).toFixed(8);
try {
const sendRequest = await qortalRequest({
action: "SEND_COIN",
coin: "LTC",
recipient: ltcRecipient,
amount: ltcAmount,
fee: ltcFeeCalculated
});
if (!sendRequest?.error) {
setLtcAmount(0);
setLtcRecipient('');
setLtcFee(30);
setOpenTxLtcSubmit(false);
setOpenSendLtcSuccess(true);
setIsLoadingWalletBalanceLtc(true);
await timeoutDelay(3000);
getWalletBalanceLtc();
}
} catch (error) {
setLtcAmount(0);
setLtcRecipient('');
setLtcFee(30);
setOpenTxLtcSubmit(false);
setOpenSendLtcError(true);
setIsLoadingWalletBalanceLtc(true);
await timeoutDelay(3000);
getWalletBalanceLtc();
console.error("ERROR SENDING LTC", error);
}
}
const LtcSendDialogPage = () => { const LtcSendDialogPage = () => {
return ( return (
<Dialog <Dialog
@@ -414,6 +564,58 @@ export default function LitecoinWallet() {
onClose={handleCloseLtcSend} onClose={handleCloseLtcSend}
slots={{ transition: Transition }} slots={{ transition: Transition }}
> >
<LtcSubmittDialog
fullWidth={true}
maxWidth='xs'
open={openTxLtcSubmit}
>
<DialogContent>
<Box sx={{ display: 'flex', justifyContent: 'center', flexWrap: 'wrap' }}>
<div style={{
width: "100%",
display: 'flex',
justifyContent: 'center'
}}>
<CircularProgress color="success" size={64} />
</div>
<div style={{
width: "100%",
display: 'flex',
justifyContent: 'center',
marginTop: '20px'
}}>
<Typography variant="h6" sx={{ color: 'primary.main', fontStyle: 'italic', fontWeight: 700 }}>
Processing Transaction Please Wait...
</Typography>
</div>
</Box>
</DialogContent>
</LtcSubmittDialog>
<Snackbar
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
open={openSendLtcSuccess}
autoHideDuration={4000}
slots={{ transition: SlideTransition }}
onClose={handleCloseSendLtcSuccess}>
<Alert
onClose={handleCloseSendLtcSuccess}
severity="success"
variant="filled"
sx={{ width: '100%' }}
>
Sent LTC transaction was successful!
</Alert>
</Snackbar>
<Snackbar open={openSendLtceError} autoHideDuration={4000} onClose={handleCloseSendLtcError}>
<Alert
onClose={handleCloseSendLtcError}
severity="error"
variant="filled"
sx={{ width: '100%' }}
>
Something went wrong, please try again!
</Alert>
</Snackbar>
<AppBar sx={{ position: 'static' }}> <AppBar sx={{ position: 'static' }}>
<Toolbar> <Toolbar>
<IconButton <IconButton
@@ -436,10 +638,11 @@ export default function LitecoinWallet() {
Transfer LTC Transfer LTC
</Typography> </Typography>
<Button <Button
disabled={validateCanSendLtc()}
variant="contained" variant="contained"
startIcon={<Send />} startIcon={<Send />}
aria-label="send" aria-label="send-ltc"
onClick={handleOpenLtcSend} onClick={sendLtcRequest}
sx={{ backgroundColor: "#05a2e4", color: "white", "&:hover": { backgroundColor: "#02648d", } }} sx={{ backgroundColor: "#05a2e4", color: "white", "&:hover": { backgroundColor: "#02648d", } }}
> >
SEND SEND
@@ -467,9 +670,48 @@ export default function LitecoinWallet() {
gutterBottom gutterBottom
sx={{ color: 'text.primary', fontWeight: 700 }} sx={{ color: 'text.primary', fontWeight: 700 }}
> >
{walletBalanceLtc + " LTC"} {isLoadingWalletBalanceLtc ? <Box sx={{ width: '175px' }}><LinearProgress /></Box> : walletBalanceLtc + " LTC"}
</Typography> </Typography>
</div> </div>
<div style={{
width: "100%",
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginTop: '20px'
}}>
<Typography
variant="h5"
align="center"
sx={{ color: 'primary.main', fontWeight: 700 }}
>
Max Sendable:&nbsp;&nbsp;
</Typography>
<Typography
variant="h5"
align="center"
sx={{ color: 'text.primary', fontWeight: 700 }}
>
{(() => {
const newMaxLtcAmount = parseFloat((walletBalanceLtc - ((ltcFee * 1000) / 1e8)).toFixed(8));
if (newMaxLtcAmount < 0) {
return Number(0.00000000) + " LTC"
} else {
return newMaxLtcAmount + " LTC"
}
})()}
</Typography>
<div style={{ marginInlineStart: '15px' }}>
<Button
variant="outlined"
size="small"
onClick={handleSendMaxLtc}
style={{ borderRadius: 50 }}
>
Send Max
</Button>
</div>
</div>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@@ -482,22 +724,32 @@ export default function LitecoinWallet() {
> >
<NumericFormat <NumericFormat
decimalScale={8} decimalScale={8}
value={amountLtc} defaultValue={0}
value={ltcAmount}
allowNegative={false} allowNegative={false}
customInput={TextField} customInput={TextField}
valueIsNumericString valueIsNumericString
variant="outlined" variant="outlined"
label="Amount (LTC)" label="Amount (LTC)"
isAllowed={(values) => {
const maxLtcCoin = (walletBalanceLtc - (ltcFee * 1000) / 1e8);
const { formattedValue, floatValue } = values;
return formattedValue === "" || floatValue <= maxLtcCoin;
}}
onValueChange={(values) => { onValueChange={(values) => {
setAmountLtc(values.floatValue); setLtcAmount(values.floatValue);
}} }}
required required
/> />
<TextField <TextField
required required
label="Receiver Adress" label="Receiver Adress"
id="ltcaddress" id="ltca-address"
margin="normal" margin="normal"
value={ltcRecipient}
helperText="Ltc Address 34 Characters long !"
slotProps={{ htmlInput: { maxLength: 34, minLength: 34 } }}
onChange={(e) => setLtcRecipient(e.target.value)}
/> />
</Box> </Box>
<div style={{ <div style={{
@@ -514,8 +766,8 @@ export default function LitecoinWallet() {
flexDirection: 'column', flexDirection: 'column',
width: '50ch' width: '50ch'
}}> }}>
<Typography id="doge-fee-slider" gutterBottom> <Typography id="ltc-fee-slider" gutterBottom>
Current fee per byte : {feeLtc} SAT Current fee per byte : {ltcFee} SAT
</Typography> </Typography>
<Slider <Slider
track={false} track={false}
@@ -523,12 +775,18 @@ export default function LitecoinWallet() {
min={10} min={10}
max={100} max={100}
valueLabelDisplay="auto" valueLabelDisplay="auto"
aria-labelledby="doge-fee-slider" aria-labelledby="ltc-fee-slider"
getAriaValueText={valueTextLtc} getAriaValueText={valueTextLtc}
defaultValue={30} defaultValue={30}
marks={ltcMarks} marks={ltcMarks}
onChange={handleChangeLtcFee} onChange={handleChangeLtcFee}
/> />
<Typography
align="center"
sx={{ fontWeight: 600, fontSize: '14px', marginTop: '15px' }}
>
Low fees may result in slow or unconfirmed transactions.
</Typography>
</Box> </Box>
</div> </div>
</Dialog> </Dialog>
@@ -577,37 +835,40 @@ export default function LitecoinWallet() {
? transactionsLtc.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) ? transactionsLtc.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
: transactionsLtc : transactionsLtc
).map((row: { ).map((row: {
name: React.Key; inputs: { address: any; addressInWallet: boolean; }[];
inputs: { address: any; }[]; outputs: { address: any; addressInWallet: boolean; }[];
outputs: { address: any; }[];
txHash: string; txHash: string;
totalAmount: any; totalAmount: any;
timestamp: number; timestamp: number;
}) => ( }) => (
<StyledTableRow key={row.name}> <StyledTableRow>
<StyledTableCell style={{ width: 'auto' }} align="left"> <StyledTableCell style={{ width: 'auto' }} align="left">
{(() => { {(() => {
if (allWalletAddressesLtc.some(ltcAll => ltcAll.address === row?.inputs[0].address)) { if (row?.totalAmount < 0) {
return <div style={{ color: '#05a2e4' }}>{row?.inputs[0].address}</div>; let meWasSender = row?.outputs.filter(function (item: { addressInWallet: boolean; }) {
return item.addressInWallet === true;
});
return <div style={{ color: '#05a2e4' }}>{meWasSender[0]?.address}</div>;
} else { } else {
return row?.inputs[0].address; let meWasNotSender = row?.outputs.filter(function (item: { addressInWallet: boolean; }) {
return item.addressInWallet === false;
});
return meWasNotSender[0].address;
} }
})()} })()}
</StyledTableCell> </StyledTableCell>
<StyledTableCell style={{ width: 'auto' }} align="left"> <StyledTableCell style={{ width: 'auto' }} align="left">
{(() => { {(() => {
if (row?.outputs[0].address === row?.inputs[0].address) { if (row?.totalAmount < 0) {
if (allWalletAddressesLtc.some(ltcAll => ltcAll.address === row?.outputs[1].address)) { let meWasNotRecipient = row?.outputs.filter(function (item: { addressInWallet: boolean; }) {
return <div style={{ color: '#05a2e4' }}>{row?.outputs[1].address}</div>; return item.addressInWallet === false;
} else { });
return row?.outputs[1].address; return meWasNotRecipient[0].address;
} } else if (row?.totalAmount > 0) {
} else { let meWasRecipient = row?.outputs.filter(function (item: { addressInWallet: boolean; }) {
if (allWalletAddressesLtc.some(ltcAll => ltcAll.address === row?.outputs[0].address)) { return item.addressInWallet === true;
return <div style={{ color: '#05a2e4' }}>{row?.outputs[0].address}</div>; });
} else { return <div style={{ color: '#05a2e4' }}>{meWasRecipient[0]?.address}</div>
return row?.outputs[0].address;
}
} }
})()} })()}
</StyledTableCell> </StyledTableCell>
@@ -620,7 +881,7 @@ export default function LitecoinWallet() {
</CustomWidthTooltip> </CustomWidthTooltip>
</StyledTableCell> </StyledTableCell>
<StyledTableCell style={{ width: 'auto' }} align="left"> <StyledTableCell style={{ width: 'auto' }} align="left">
{row?.outputs[0].address === walletInfoLtc?.address ? {row?.totalAmount > 0 ?
<div style={{ color: '#66bb6a' }}>+{(Number(row?.totalAmount) / 1e8).toFixed(8)}</div> : <div style={{ color: '#f44336' }}>{(Number(row?.totalAmount) / 1e8).toFixed(8)}</div> <div style={{ color: '#66bb6a' }}>+{(Number(row?.totalAmount) / 1e8).toFixed(8)}</div> : <div style={{ color: '#f44336' }}>{(Number(row?.totalAmount) / 1e8).toFixed(8)}</div>
} }
</StyledTableCell> </StyledTableCell>
@@ -662,10 +923,76 @@ export default function LitecoinWallet() {
); );
} }
const setNewCurrentLtcServer = async (typeServer: string, hostServer: string, portServer: number) => {
try {
const setServer = await qortalRequest({
action: "SET_CURRENT_FOREIGN_SERVER",
coin: "LTC",
type: typeServer,
host: hostServer,
port: portServer
});
if (!setServer?.error) {
await getElectrumServersLtc();
setOpenLtcElectrum(false);
await getWalletBalanceLtc();
await getTransactionsLtc();
}
} catch (error) {
await getElectrumServersLtc();
setOpenLtcElectrum(false);
console.error("ERROR GET LTC SERVERS INFO", error);
}
}
const LtcElectrumDialogPage = () => {
return (
<LtcElectrumDialog
onClose={handleCloseLtcQR}
aria-labelledby="ltc-electrum-servers"
open={openLtcElectrum}
keepMounted={false}
>
<DialogTitle sx={{ m: 0, p: 2, fontSize: '14px' }} id="ltc-electrum-servers">
Available Litecoin Electrum Servers.
</DialogTitle>
<DialogContent dividers>
<Box sx={{
width: '100%',
maxWidth: 500,
position: 'relative',
overflow: 'auto',
maxHeight: 400
}}>
<List>
{(
allElectrumServersLtc
).map((server: {
connectionType: string;
hostName: string;
port: number;
}) => (
<ListItemButton onClick={() => { setNewCurrentLtcServer(server?.connectionType, server?.hostName, server?.port) }}>
<ListItemText primary={server?.hostName + ':' + server?.port} />
</ListItemButton>
))}
</List>
</Box>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleCloseLtcElectrum}>
CLOSE
</Button>
</DialogActions>
</LtcElectrumDialog>
);
}
return ( return (
<Box sx={{ width: '100%', marginTop: "20px" }}> <Box sx={{ width: '100%', marginTop: "20px" }}>
{LtcSendDialogPage()} {LtcSendDialogPage()}
{LtcQrDialogPage()} {LtcQrDialogPage()}
{LtcElectrumDialogPage()}
<Typography gutterBottom variant="h5" sx={{ color: 'primary.main', fontStyle: 'italic', fontWeight: 700 }}> <Typography gutterBottom variant="h5" sx={{ color: 'primary.main', fontStyle: 'italic', fontWeight: 700 }}>
Litecoin Wallet Litecoin Wallet
</Typography> </Typography>
@@ -717,12 +1044,38 @@ export default function LitecoinWallet() {
> >
{walletInfoLtc?.address} {walletInfoLtc?.address}
</Typography> </Typography>
<Tooltip placement="top" title={copyLtcAddress ? copyLtcAddress : "Copy Address"}> <Tooltip placement="right" title={copyLtcAddress ? copyLtcAddress : "Copy Address"}>
<IconButton aria-label="copy" size="small" onClick={() => { navigator.clipboard.writeText(walletInfoLtc?.address), changeCopyLtcStatus() }}> <IconButton aria-label="copy" size="small" onClick={() => { navigator.clipboard.writeText(walletInfoLtc?.address), changeCopyLtcStatus() }}>
<CopyAllTwoTone fontSize="small" /> <CopyAllTwoTone fontSize="small" />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
</div> </div>
<div style={{
width: "100%",
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<Typography
variant="subtitle1"
align="center"
sx={{ color: 'primary.main', fontWeight: 700 }}
>
Electrum Server:&nbsp;&nbsp;
</Typography>
<Typography
variant="subtitle1"
align="center"
sx={{ color: 'text.primary', fontWeight: 700 }}
>
{currentElectrumServerLtc[0]?.hostName + ":" + currentElectrumServerLtc[0]?.port}
</Typography>
<Tooltip placement="right" title="CHange Server">
<IconButton aria-label="open-electrum" size="small" onClick={handleOpenLtcElectrum}>
<PublishedWithChangesTwoTone fontSize="small" />
</IconButton>
</Tooltip>
</div>
<div style={{ <div style={{
width: "100%", width: "100%",
display: 'flex', display: 'flex',
@@ -755,9 +1108,27 @@ export default function LitecoinWallet() {
Address Book Address Book
</WalletButtons> </WalletButtons>
</div> </div>
<Typography variant="h6" paddingTop={2} paddingBottom={2}> <div style={{
Transactions: width: "100%",
</Typography> display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}}>
<Typography variant="h6" paddingTop={2} paddingBottom={2}>
Transactions:
</Typography>
<Button
size="small"
onClick={handleLoadingRefreshLtc}
loading={loadingRefreshLtc}
loadingPosition="start"
startIcon={<Refresh />}
variant="outlined"
style={{ borderRadius: 50 }}
>
Refresh
</Button>
</div>
{isLoadingLtcTransactions ? tableLoader() : transactionsTable()} {isLoadingLtcTransactions ? tableLoader() : transactionsTable()}
</WalleteCard> </WalleteCard>
</Box> </Box>

File diff suppressed because it is too large Load Diff