From 55ea36c7cff2aebf565fa5bbbf223671e082a7aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 18:06:16 +0000 Subject: [PATCH 01/12] Bump axios from 1.7.7 to 1.8.2 Bumps [axios](https://github.com/axios/axios) from 1.7.7 to 1.8.2. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.7.7...v1.8.2) --- updated-dependencies: - dependency-name: axios dependency-version: 1.8.2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package-lock.json | 9 +++++---- package.json | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index fbba254..11070dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,7 @@ "@uiw/react-color": "^2.5.1", "adm-zip": "^0.5.16", "asmcrypto.js": "2.3.2", - "axios": "^1.7.7", + "axios": "^1.8.2", "bcryptjs": "2.4.3", "buffer": "6.0.3", "chokidar": "^3.6.0", @@ -6963,9 +6963,10 @@ } }, "node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", + "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", diff --git a/package.json b/package.json index 2869cb9..2f922de 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "@uiw/react-color": "^2.5.1", "adm-zip": "^0.5.16", "asmcrypto.js": "2.3.2", - "axios": "^1.7.7", + "axios": "^1.8.2", "bcryptjs": "2.4.3", "buffer": "6.0.3", "chokidar": "^3.6.0", From 9fcbd8aaffcfd9cb08faa6fb26bc549cdd730e0a Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 11:27:14 +0200 Subject: [PATCH 02/12] Rename context name --- src/App.tsx | 7 ++++--- src/Wallets.tsx | 4 ++-- src/components/Apps/AppPublish.tsx | 4 ++-- src/components/Apps/AppRating.tsx | 4 ++-- src/components/Apps/AppsDesktop.tsx | 4 ++-- src/components/Apps/AppsDevModeHome.tsx | 4 ++-- src/components/Apps/AppsPrivate.tsx | 4 ++-- src/components/Chat/AdminSpaceInner.tsx | 4 ++-- src/components/Chat/ChatGroup.tsx | 4 ++-- src/components/Chat/CreateCommonSecret.tsx | 4 ++-- src/components/Chat/GroupAnnouncements.tsx | 4 ++-- src/components/Chat/GroupAvatar.tsx | 4 ++-- src/components/Chat/MessageItem.tsx | 4 ++-- src/components/Embeds/AttachmentEmbed.tsx | 4 ++-- src/components/Embeds/PollEmbed.tsx | 4 ++-- src/components/Embeds/VideoPlayer.tsx | 4 ++-- src/components/GlobalActions/JoinGroup.tsx | 4 ++-- src/components/Group/AddGroup.tsx | 4 ++-- src/components/Group/AddGroupList.tsx | 4 ++-- src/components/Group/BlockedUsersModal.tsx | 4 ++-- src/components/Group/Forum/NewThread.tsx | 8 ++++++-- src/components/Group/ListOfGroupPromotions.tsx | 4 ++-- src/components/Group/ManageMembers.tsx | 4 ++-- src/components/Group/Settings.tsx | 5 +++-- src/components/Group/UserListOfInvites.tsx | 4 ++-- src/components/MainAvatar.tsx | 8 ++++++-- src/components/NotAuthenticated.tsx | 5 +++-- src/components/Save/Save.tsx | 4 ++-- src/components/Tutorials/Tutorials.tsx | 5 +++-- src/components/WrapperUserAction.tsx | 4 ++-- src/hooks/useHandlePrivateApps.tsx | 4 ++-- src/hooks/useQortalMessageListener.tsx | 4 ++-- 32 files changed, 77 insertions(+), 65 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index d756f44..777454d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -236,7 +236,8 @@ const defaultValuesGlobal = { setOpenTutorialModal: () => {}, }; -export const MyContext = createContext(defaultValues); +export const QORTAL_APP_CONTEXT = + createContext(defaultValues); export let globalApiKey: string | null = null; @@ -2016,7 +2017,7 @@ function App() { > - + {extState === 'not-authenticated' && ( - + {extState === 'create-wallet' && walletToBeDownloaded && ( { @@ -44,7 +44,7 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => { const [seedValue, setSeedValue] = useState(''); const [seedName, setSeedName] = useState(''); const [seedError, setSeedError] = useState(''); - const { hasSeenGettingStarted } = useContext(MyContext); + const { hasSeenGettingStarted } = useContext(QORTAL_APP_CONTEXT); const [password, setPassword] = useState(''); const [isOpenSeedModal, setIsOpenSeedModal] = useState(false); const [isLoadingEncryptSeed, setIsLoadingEncryptSeed] = useState(false); diff --git a/src/components/Apps/AppPublish.tsx b/src/components/Apps/AppPublish.tsx index 7dfcf3c..7800c55 100644 --- a/src/components/Apps/AppPublish.tsx +++ b/src/components/Apps/AppPublish.tsx @@ -16,7 +16,7 @@ import { useTheme, } from '@mui/material'; import { styled } from '@mui/system'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { Spacer } from '../../common/Spacer'; import { executeEvent } from '../../utils/events'; import { useDropzone } from 'react-dropzone'; @@ -65,7 +65,7 @@ export const AppPublish = ({ names, categories }) => { const [category, setCategory] = useState(''); const [appType, setAppType] = useState('APP'); const [file, setFile] = useState(null); - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); const { t } = useTranslation(['auth', 'core', 'group']); const [tag1, setTag1] = useState(''); diff --git a/src/components/Apps/AppRating.tsx b/src/components/Apps/AppRating.tsx index 17d2cf0..9d63213 100644 --- a/src/components/Apps/AppRating.tsx +++ b/src/components/Apps/AppRating.tsx @@ -1,7 +1,7 @@ import { Box, Rating } from '@mui/material'; import { useCallback, useContext, useEffect, useRef, useState } from 'react'; import { getFee } from '../../background'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { StarFilledIcon } from '../../assets/Icons/StarFilled'; import { StarEmptyIcon } from '../../assets/Icons/StarEmpty'; @@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next'; export const AppRating = ({ app, myName, ratingCountPosition = 'right' }) => { const [value, setValue] = useState(0); - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const [hasPublishedRating, setHasPublishedRating] = useState( null ); diff --git a/src/components/Apps/AppsDesktop.tsx b/src/components/Apps/AppsDesktop.tsx index 216f5e7..0ef5366 100644 --- a/src/components/Apps/AppsDesktop.tsx +++ b/src/components/Apps/AppsDesktop.tsx @@ -1,7 +1,7 @@ import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; import { AppsHomeDesktop } from './AppsHomeDesktop'; import { Spacer } from '../../common/Spacer'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { AppInfo } from './AppInfo'; import { executeEvent, @@ -48,7 +48,7 @@ export const AppsDesktop = ({ const [categories, setCategories] = useState([]); const iframeRefs = useRef({}); const [isEnabledDevMode, setIsEnabledDevMode] = useAtom(enabledDevModeAtom); - const { showTutorial } = useContext(MyContext); + const { showTutorial } = useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); const { t } = useTranslation(['auth', 'core', 'group']); diff --git a/src/components/Apps/AppsDevModeHome.tsx b/src/components/Apps/AppsDevModeHome.tsx index 74bce21..d21618b 100644 --- a/src/components/Apps/AppsDevModeHome.tsx +++ b/src/components/Apps/AppsDevModeHome.tsx @@ -19,7 +19,7 @@ import { Input, } from '@mui/material'; import { Add } from '@mui/icons-material'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { executeEvent } from '../../utils/events'; import { Spacer } from '../../common/Spacer'; import { useModal } from '../../common/useModal'; @@ -48,7 +48,7 @@ export const AppsDevModeHome = ({ setOpenSnackGlobal, infoSnackCustom, setInfoSnackCustom, - } = useContext(MyContext); + } = useContext(QORTAL_APP_CONTEXT); const handleSelectFile = async (existingFilePath) => { const filePath = existingFilePath || (await window.electron.selectFile()); diff --git a/src/components/Apps/AppsPrivate.tsx b/src/components/Apps/AppsPrivate.tsx index 604a8f5..64d5045 100644 --- a/src/components/Apps/AppsPrivate.tsx +++ b/src/components/Apps/AppsPrivate.tsx @@ -31,7 +31,7 @@ import { } from './Apps-styles'; import AddIcon from '@mui/icons-material/Add'; import ImageUploader from '../../common/ImageUploader'; -import { MyContext } from '../../App'; +import { QORTAL_APP_CONTEXT } from '../../App'; import { fileToBase64 } from '../../utils/fileReading'; import { objectToBase64 } from '../../qdn/encryption/group-encryption'; import { getFee } from '../../background'; @@ -59,7 +59,7 @@ export const AppsPrivate = ({ myName }) => { const [isOpenPrivateModal, setIsOpenPrivateModal] = useState(false); const { show, setInfoSnackCustom, setOpenSnackGlobal } = - useContext(MyContext); + useContext(QORTAL_APP_CONTEXT); const [memberGroups] = useAtom(memberGroupsAtom); const theme = useTheme(); diff --git a/src/components/Chat/AdminSpaceInner.tsx b/src/components/Chat/AdminSpaceInner.tsx index 0312e5d..0a68048 100644 --- a/src/components/Chat/AdminSpaceInner.tsx +++ b/src/components/Chat/AdminSpaceInner.tsx @@ -1,6 +1,6 @@ import { useCallback, useContext, useEffect, useState } from 'react'; import { - MyContext, + QORTAL_APP_CONTEXT, getArbitraryEndpointReact, getBaseApiReact, } from '../../App'; @@ -73,7 +73,7 @@ export const AdminSpaceInner = ({ useState(null); const [isLoadingPublishKey, setIsLoadingPublishKey] = useState(false); const { show, setInfoSnackCustom, setOpenSnackGlobal } = - useContext(MyContext); + useContext(QORTAL_APP_CONTEXT); const { t } = useTranslation(['auth', 'core', 'group']); const getAdminGroupSecretKey = useCallback(async () => { diff --git a/src/components/Chat/ChatGroup.tsx b/src/components/Chat/ChatGroup.tsx index 1e65eb7..fcd6bbd 100644 --- a/src/components/Chat/ChatGroup.tsx +++ b/src/components/Chat/ChatGroup.tsx @@ -19,7 +19,7 @@ import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; import { getBaseApiReact, getBaseApiReactSocket, - MyContext, + QORTAL_APP_CONTEXT, pauseAllQueues, resumeAllQueues, } from '../../App'; @@ -71,7 +71,7 @@ export const ChatGroup = ({ hideView, isPrivate, }) => { - const { isUserBlocked, show } = useContext(MyContext); + const { isUserBlocked, show } = useContext(QORTAL_APP_CONTEXT); const [messages, setMessages] = useState([]); const [chatReferences, setChatReferences] = useState({}); const [isSending, setIsSending] = useState(false); diff --git a/src/components/Chat/CreateCommonSecret.tsx b/src/components/Chat/CreateCommonSecret.tsx index bb5834e..e7472f6 100644 --- a/src/components/Chat/CreateCommonSecret.tsx +++ b/src/components/Chat/CreateCommonSecret.tsx @@ -3,7 +3,7 @@ import { Box, Button, Typography, useTheme } from '@mui/material'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { LoadingButton } from '@mui/lab'; import { - MyContext, + QORTAL_APP_CONTEXT, getArbitraryEndpointReact, getBaseApiReact, pauseAllQueues, @@ -32,7 +32,7 @@ export const CreateCommonSecret = ({ setIsForceShowCreationKeyPopup, isForceShowCreationKeyPopup, }) => { - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const setTxList = useSetAtom(txListAtom); const [openSnack, setOpenSnack] = useState(false); diff --git a/src/components/Chat/GroupAnnouncements.tsx b/src/components/Chat/GroupAnnouncements.tsx index 0ece390..a9d1e0a 100644 --- a/src/components/Chat/GroupAnnouncements.tsx +++ b/src/components/Chat/GroupAnnouncements.tsx @@ -24,7 +24,7 @@ import { AnnouncementList } from './AnnouncementList'; import CampaignIcon from '@mui/icons-material/Campaign'; import { AnnouncementDiscussion } from './AnnouncementDiscussion'; import { - MyContext, + QORTAL_APP_CONTEXT, getArbitraryEndpointReact, getBaseApiReact, pauseAllQueues, @@ -142,7 +142,7 @@ export const GroupAnnouncements = ({ const [selectedAnnouncement, setSelectedAnnouncement] = useState(null); const [isFocusedParent, setIsFocusedParent] = useState(false); - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const [openSnack, setOpenSnack] = useState(false); const [infoSnack, setInfoSnack] = useState(null); const hasInitialized = useRef(false); diff --git a/src/components/Chat/GroupAvatar.tsx b/src/components/Chat/GroupAvatar.tsx index 13a0745..5c28899 100644 --- a/src/components/Chat/GroupAvatar.tsx +++ b/src/components/Chat/GroupAvatar.tsx @@ -1,7 +1,7 @@ import { useCallback, useContext, useEffect, useState } from 'react'; import Logo2 from '../../assets/svgs/Logo2.svg'; import { - MyContext, + QORTAL_APP_CONTEXT, getArbitraryEndpointReact, getBaseApiReact, } from '../../App'; @@ -32,7 +32,7 @@ export const GroupAvatar = ({ const [hasAvatar, setHasAvatar] = useState(false); const [avatarFile, setAvatarFile] = useState(null); const [tempAvatar, setTempAvatar] = useState(null); - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const { t } = useTranslation(['auth', 'core', 'group']); const [anchorEl, setAnchorEl] = useState(null); const [isLoading, setIsLoading] = useState(false); diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index a2e7fd5..a429846 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -22,7 +22,7 @@ import { useTheme, } from '@mui/material'; import { formatTimestamp } from '../../utils/time'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { generateHTML } from '@tiptap/react'; import Highlight from '@tiptap/extension-highlight'; import Mention from '@tiptap/extension-mention'; @@ -113,7 +113,7 @@ export const MessageItem = memo( onEdit, isPrivate, }) => { - const { getIndividualUserInfo } = useContext(MyContext); + const { getIndividualUserInfo } = useContext(QORTAL_APP_CONTEXT); const [anchorEl, setAnchorEl] = useState(null); const [selectedReaction, setSelectedReaction] = useState(null); const [userInfo, setUserInfo] = useState(null); diff --git a/src/components/Embeds/AttachmentEmbed.tsx b/src/components/Embeds/AttachmentEmbed.tsx index 5a30d2d..d7458a7 100644 --- a/src/components/Embeds/AttachmentEmbed.tsx +++ b/src/components/Embeds/AttachmentEmbed.tsx @@ -1,5 +1,5 @@ import { useContext, useState } from 'react'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { Card, CardContent, @@ -36,7 +36,7 @@ export const AttachmentCard = ({ selectedGroupId, }) => { const [isOpen, setIsOpen] = useState(true); - const { downloadResource } = useContext(MyContext); + const { downloadResource } = useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); const { t } = useTranslation(['auth', 'core', 'group']); diff --git a/src/components/Embeds/PollEmbed.tsx b/src/components/Embeds/PollEmbed.tsx index cffe724..bc246ba 100644 --- a/src/components/Embeds/PollEmbed.tsx +++ b/src/components/Embeds/PollEmbed.tsx @@ -1,5 +1,5 @@ import { useContext, useEffect, useState } from 'react'; -import { MyContext } from '../../App'; +import { QORTAL_APP_CONTEXT } from '../../App'; import { Card, CardContent, @@ -37,7 +37,7 @@ export const PollCard = ({ const [ownerName, setOwnerName] = useState(''); const [showResults, setShowResults] = useState(false); const [isOpen, setIsOpen] = useState(false); - const { show, userInfo } = useContext(MyContext); + const { show, userInfo } = useContext(QORTAL_APP_CONTEXT); const [isLoadingSubmit, setIsLoadingSubmit] = useState(false); const theme = useTheme(); const { t } = useTranslation(['auth', 'core', 'group']); diff --git a/src/components/Embeds/VideoPlayer.tsx b/src/components/Embeds/VideoPlayer.tsx index 958b2ad..ab9d3a6 100644 --- a/src/components/Embeds/VideoPlayer.tsx +++ b/src/components/Embeds/VideoPlayer.tsx @@ -21,7 +21,7 @@ import { } from '@mui/icons-material'; import { styled } from '@mui/system'; import { Refresh } from '@mui/icons-material'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { resourceKeySelector } from '../../atoms/global'; import { useAtomValue } from 'jotai'; @@ -88,7 +88,7 @@ export const VideoPlayer: FC = ({ }, [service, name, identifier]); const download = useAtomValue(resourceKeySelector(keyIdentifier)); - const { downloadResource } = useContext(MyContext); + const { downloadResource } = useContext(QORTAL_APP_CONTEXT); const videoRef = useRef(null); const [playing, setPlaying] = useState(false); const [volume, setVolume] = useState(1); diff --git a/src/components/GlobalActions/JoinGroup.tsx b/src/components/GlobalActions/JoinGroup.tsx index de3fd22..48ea442 100644 --- a/src/components/GlobalActions/JoinGroup.tsx +++ b/src/components/GlobalActions/JoinGroup.tsx @@ -11,7 +11,7 @@ import { useTheme, } from '@mui/material'; import { CustomButtonAccept } from '../../styles/App-styles'; -import { getBaseApiReact, MyContext } from '../../App'; +import { getBaseApiReact, QORTAL_APP_CONTEXT } from '../../App'; import { getFee } from '../../background'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { FidgetSpinner } from 'react-loader-spinner'; @@ -20,7 +20,7 @@ import { memberGroupsAtom, txListAtom } from '../../atoms/global'; import { useTranslation } from 'react-i18next'; export const JoinGroup = () => { - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const setTxList = useSetAtom(txListAtom); const [memberGroups] = useAtom(memberGroupsAtom); const [openSnack, setOpenSnack] = useState(false); diff --git a/src/components/Group/AddGroup.tsx b/src/components/Group/AddGroup.tsx index 1916aaa..2e0ca92 100644 --- a/src/components/Group/AddGroup.tsx +++ b/src/components/Group/AddGroup.tsx @@ -35,7 +35,7 @@ import { AddGroupList } from './AddGroupList'; import { UserListOfInvites } from './UserListOfInvites'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { getFee } from '../../background'; -import { MyContext } from '../../App'; +import { QORTAL_APP_CONTEXT } from '../../App'; import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; import { useTranslation } from 'react-i18next'; import { useSetAtom } from 'jotai'; @@ -59,7 +59,7 @@ const Transition = forwardRef(function Transition( }); export const AddGroup = ({ address, open, setOpen }) => { - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const setTxList = useSetAtom(txListAtom); const [openAdvance, setOpenAdvance] = useState(false); diff --git a/src/components/Group/AddGroupList.tsx b/src/components/Group/AddGroupList.tsx index eaa7f4d..e9316d8 100644 --- a/src/components/Group/AddGroupList.tsx +++ b/src/components/Group/AddGroupList.tsx @@ -23,7 +23,7 @@ import { List, } from 'react-virtualized'; import _ from 'lodash'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { LoadingButton } from '@mui/lab'; import { getFee } from '../../background'; import LockIcon from '@mui/icons-material/Lock'; @@ -39,7 +39,7 @@ const cache = new CellMeasurerCache({ }); export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => { - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const [memberGroups] = useAtom(memberGroupsAtom); const setTxList = useSetAtom(txListAtom); const { t } = useTranslation(['auth', 'core', 'group']); diff --git a/src/components/Group/BlockedUsersModal.tsx b/src/components/Group/BlockedUsersModal.tsx index 99c9191..c8145b3 100644 --- a/src/components/Group/BlockedUsersModal.tsx +++ b/src/components/Group/BlockedUsersModal.tsx @@ -11,7 +11,7 @@ import { useTheme, } from '@mui/material'; import { useContext, useEffect, useState } from 'react'; -import { getBaseApiReact, MyContext } from '../../App'; +import { getBaseApiReact, QORTAL_APP_CONTEXT } from '../../App'; import { Spacer } from '../../common/Spacer'; import { executeEvent, @@ -42,7 +42,7 @@ export const BlockedUsersModal = () => { addToBlockList, setOpenSnackGlobal, setInfoSnackCustom, - } = useContext(MyContext); + } = useContext(QORTAL_APP_CONTEXT); const [blockedUsers, setBlockedUsers] = useState({ addresses: {}, diff --git a/src/components/Group/Forum/NewThread.tsx b/src/components/Group/Forum/NewThread.tsx index 763ecb6..b9d8048 100644 --- a/src/components/Group/Forum/NewThread.tsx +++ b/src/components/Group/Forum/NewThread.tsx @@ -17,7 +17,11 @@ import { ReusableModal } from './ReusableModal'; import { Spacer } from '../../../common/Spacer'; import { CreateThreadIcon } from '../../../assets/Icons/CreateThreadIcon'; import { SendNewMessage } from '../../../assets/Icons/SendNewMessage'; -import { MyContext, pauseAllQueues, resumeAllQueues } from '../../../App'; +import { + QORTAL_APP_CONTEXT, + pauseAllQueues, + resumeAllQueues, +} from '../../../App'; import { getFee } from '../../../background'; import TipTap from '../../Chat/TipTap'; import { MessageDisplay } from '../../Chat/MessageDisplay'; @@ -141,7 +145,7 @@ export const NewThread = ({ isPrivate, }: NewMessageProps) => { const { t } = useTranslation(['auth', 'core', 'group']); - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const [isOpen, setIsOpen] = useState(false); const [value, setValue] = useState(''); const [isSending, setIsSending] = useState(false); diff --git a/src/components/Group/ListOfGroupPromotions.tsx b/src/components/Group/ListOfGroupPromotions.tsx index bf6ba8b..8c772cd 100644 --- a/src/components/Group/ListOfGroupPromotions.tsx +++ b/src/components/Group/ListOfGroupPromotions.tsx @@ -21,7 +21,7 @@ import { LoadingButton } from '@mui/lab'; import LockIcon from '@mui/icons-material/Lock'; import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred'; import { - MyContext, + QORTAL_APP_CONTEXT, getArbitraryEndpointReact, getBaseApiReact, } from '../../App'; @@ -88,7 +88,7 @@ export const ListOfGroupPromotions = () => { const [fee, setFee] = useState(null); const [isLoadingJoinGroup, setIsLoadingJoinGroup] = useState(false); const [isLoadingPublish, setIsLoadingPublish] = useState(false); - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const setTxList = useSetAtom(txListAtom); const theme = useTheme(); const { t } = useTranslation(['auth', 'core', 'group']); diff --git a/src/components/Group/ManageMembers.tsx b/src/components/Group/ManageMembers.tsx index e64eeab..9fd3150 100644 --- a/src/components/Group/ManageMembers.tsx +++ b/src/components/Group/ManageMembers.tsx @@ -25,7 +25,7 @@ import { ListOfBans } from './ListOfBans'; import { ListOfJoinRequests } from './ListOfJoinRequests'; import { Box, ButtonBase, Card, Tab, Tabs, useTheme } from '@mui/material'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { getGroupMembers, getNames } from './Group'; import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; import { getFee } from '../../background'; @@ -72,7 +72,7 @@ export const ManageMembers = ({ }; const theme = useTheme(); const { t } = useTranslation(['auth', 'core', 'group']); - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const setTxList = useSetAtom(txListAtom); const handleClose = () => { diff --git a/src/components/Group/Settings.tsx b/src/components/Group/Settings.tsx index c285fa0..058adde 100644 --- a/src/components/Group/Settings.tsx +++ b/src/components/Group/Settings.tsx @@ -38,7 +38,7 @@ import { Spacer } from '../../common/Spacer'; import PhraseWallet from '../../utils/generateWallet/phrase-wallet'; import { walletVersion } from '../../background'; import Base58 from '../../deps/Base58'; -import { MyContext } from '../../App'; +import { QORTAL_APP_CONTEXT } from '../../App'; import { useTranslation } from 'react-i18next'; const LocalNodeSwitch = styled(Switch)(({ theme }) => ({ @@ -233,7 +233,8 @@ const ExportPrivateKey = ({ rawWallet }) => { const [password, setPassword] = useState(''); const [privateKey, setPrivateKey] = useState(''); const [isOpen, setIsOpen] = useState(false); - const { setOpenSnackGlobal, setInfoSnackCustom } = useContext(MyContext); + const { setOpenSnackGlobal, setInfoSnackCustom } = + useContext(QORTAL_APP_CONTEXT); const { t } = useTranslation(['auth', 'core', 'group']); const exportPrivateKeyFunc = async () => { diff --git a/src/components/Group/UserListOfInvites.tsx b/src/components/Group/UserListOfInvites.tsx index 3b32f54..064296a 100644 --- a/src/components/Group/UserListOfInvites.tsx +++ b/src/components/Group/UserListOfInvites.tsx @@ -14,7 +14,7 @@ import { CellMeasurerCache, List, } from 'react-virtualized'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { LoadingButton } from '@mui/lab'; import { getFee } from '../../background'; import LockIcon from '@mui/icons-material/Lock'; @@ -55,7 +55,7 @@ export const UserListOfInvites = ({ setInfoSnack, setOpenSnack, }) => { - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const setTxList = useSetAtom(txListAtom); const [invites, setInvites] = useState([]); diff --git a/src/components/MainAvatar.tsx b/src/components/MainAvatar.tsx index 2c40dc5..26ce2e1 100644 --- a/src/components/MainAvatar.tsx +++ b/src/components/MainAvatar.tsx @@ -1,6 +1,10 @@ import { useContext, useEffect, useState } from 'react'; import Logo2 from '../assets/svgs/Logo2.svg'; -import { MyContext, getArbitraryEndpointReact, getBaseApiReact } from '../App'; +import { + QORTAL_APP_CONTEXT, + getArbitraryEndpointReact, + getBaseApiReact, +} from '../App'; import { Avatar, Box, @@ -22,7 +26,7 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => { const [hasAvatar, setHasAvatar] = useState(false); const [avatarFile, setAvatarFile] = useState(null); const [tempAvatar, setTempAvatar] = useState(null); - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const [anchorEl, setAnchorEl] = useState(null); const [isLoading, setIsLoading] = useState(false); diff --git a/src/components/NotAuthenticated.tsx b/src/components/NotAuthenticated.tsx index a87fdce..c9be445 100644 --- a/src/components/NotAuthenticated.tsx +++ b/src/components/NotAuthenticated.tsx @@ -32,7 +32,7 @@ import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip'; import ThemeSelector from './Theme/ThemeSelector'; import { useTranslation } from 'react-i18next'; import LanguageSelector from './Language/LanguageSelector'; -import { MyContext } from '../App'; +import { QORTAL_APP_CONTEXT } from '../App'; export const manifestData = { version: '0.5.4', @@ -81,7 +81,8 @@ export const NotAuthenticated = ({ const [showSelectApiKey, setShowSelectApiKey] = useState(false); const [enteredApiKey, setEnteredApiKey] = useState(''); const [customNodeToSaveIndex, setCustomNodeToSaveIndex] = useState(null); - const { showTutorial, hasSeenGettingStarted } = useContext(MyContext); + const { showTutorial, hasSeenGettingStarted } = + useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); const { t } = useTranslation(['auth', 'core']); diff --git a/src/components/Save/Save.tsx b/src/components/Save/Save.tsx index c89b625..dd9c148 100644 --- a/src/components/Save/Save.tsx +++ b/src/components/Save/Save.tsx @@ -18,7 +18,7 @@ import { useTheme, } from '@mui/material'; import { objectToBase64 } from '../../qdn/encryption/group-encryption'; -import { MyContext } from '../../App'; +import { QORTAL_APP_CONTEXT } from '../../App'; import { getFee } from '../../background'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { SaveIcon } from '../../assets/Icons/SaveIcon'; @@ -82,7 +82,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { const [oldPinnedApps, setOldPinnedApps] = useAtom(oldPinnedAppsAtom); const [anchorEl, setAnchorEl] = useState(null); - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); const { t } = useTranslation(['auth', 'core', 'group']); diff --git a/src/components/Tutorials/Tutorials.tsx b/src/components/Tutorials/Tutorials.tsx index d480b1c..068c3ad 100644 --- a/src/components/Tutorials/Tutorials.tsx +++ b/src/components/Tutorials/Tutorials.tsx @@ -1,5 +1,5 @@ import { useContext, useState } from 'react'; -import { MyContext } from '../../App'; +import { QORTAL_APP_CONTEXT } from '../../App'; import { Button, Dialog, @@ -16,7 +16,8 @@ import { VideoPlayer } from '../Embeds/VideoPlayer'; import { useTranslation } from 'react-i18next'; export const Tutorials = () => { - const { openTutorialModal, setOpenTutorialModal } = useContext(MyContext); + const { openTutorialModal, setOpenTutorialModal } = + useContext(QORTAL_APP_CONTEXT); const [multiNumber, setMultiNumber] = useState(0); const theme = useTheme(); const { t } = useTranslation(['core', 'tutorial']); diff --git a/src/components/WrapperUserAction.tsx b/src/components/WrapperUserAction.tsx index 591c200..490333d 100644 --- a/src/components/WrapperUserAction.tsx +++ b/src/components/WrapperUserAction.tsx @@ -7,7 +7,7 @@ import { useTheme, } from '@mui/material'; import { executeEvent } from '../utils/events'; -import { MyContext } from '../App'; +import { QORTAL_APP_CONTEXT } from '../App'; import { useAtom } from 'jotai'; import { isRunningPublicNodeAtom } from '../atoms/global'; import { useTranslation } from 'react-i18next'; @@ -175,7 +175,7 @@ const BlockUser = ({ address, name, handleClose }) => { const [isAlreadyBlocked, setIsAlreadyBlocked] = useState(null); const [isLoading, setIsLoading] = useState(false); const { isUserBlocked, addToBlockList, removeBlockFromList } = - useContext(MyContext); + useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); const { t } = useTranslation(['auth', 'core', 'group']); diff --git a/src/hooks/useHandlePrivateApps.tsx b/src/hooks/useHandlePrivateApps.tsx index fdeee79..e1d8288 100644 --- a/src/hooks/useHandlePrivateApps.tsx +++ b/src/hooks/useHandlePrivateApps.tsx @@ -1,6 +1,6 @@ import { useContext, useState } from 'react'; import { executeEvent } from '../utils/events'; -import { getBaseApiReact, MyContext } from '../App'; +import { getBaseApiReact, QORTAL_APP_CONTEXT } from '../App'; import { createEndpoint } from '../background'; import { settingsLocalLastUpdatedAtom, @@ -19,7 +19,7 @@ export const useHandlePrivateApps = () => { setOpenSnackGlobal, infoSnackCustom, setInfoSnackCustom, - } = useContext(MyContext); + } = useContext(QORTAL_APP_CONTEXT); const setSortablePinnedApps = useSetAtom(sortablePinnedAppsAtom); const setSettingsLocalLastUpdated = useSetAtom(settingsLocalLastUpdatedAtom); const { t } = useTranslation(['auth', 'core', 'group']); diff --git a/src/hooks/useQortalMessageListener.tsx b/src/hooks/useQortalMessageListener.tsx index 1a8670b..acda0ca 100644 --- a/src/hooks/useQortalMessageListener.tsx +++ b/src/hooks/useQortalMessageListener.tsx @@ -4,7 +4,7 @@ import { navigationControllerAtom } from '../atoms/global'; import { Filesystem, Directory } from '@capacitor/filesystem'; import { saveFile } from '../qortalRequests/get'; import { mimeToExtensionMap } from '../utils/memeTypes'; -import { MyContext } from '../App'; +import { QORTAL_APP_CONTEXT } from '../App'; import FileSaver from 'file-saver'; import { useSetAtom } from 'jotai'; @@ -526,7 +526,7 @@ export const useQortalMessageListener = ( setOpenSnackGlobal, infoSnackCustom, setInfoSnackCustom, - } = useContext(MyContext); + } = useContext(QORTAL_APP_CONTEXT); useEffect(() => { if (tabId && !isNaN(history?.currentIndex)) { From 2d01b3e8ae9de523ffe131b5ed20ce9a10b3c75e Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 11:53:08 +0200 Subject: [PATCH 03/12] Create encryption folder and move files --- src/App.tsx | 4 +- src/Wallets.tsx | 6 +- src/{ => background}/background-cases.ts | 16 +- src/{ => background}/background.ts | 41 +- src/components/Apps/AppPublish.tsx | 2 +- src/components/Apps/AppRating.tsx | 2 +- src/components/Apps/AppsDevModeHome.tsx | 2 +- src/components/Apps/AppsPrivate.tsx | 2 +- src/components/Chat/AdminSpaceInner.tsx | 4 +- .../Chat/AnnouncementDiscussion.tsx | 2 +- src/components/Chat/ChatDirect.tsx | 2 +- src/components/Chat/ChatGroup.tsx | 2 +- src/components/Chat/CreateCommonSecret.tsx | 4 +- src/components/Chat/GroupAnnouncements.tsx | 4 +- src/components/Chat/GroupAvatar.tsx | 2 +- src/components/Embeds/PollEmbed.tsx | 2 +- src/components/GlobalActions/JoinGroup.tsx | 2 +- src/components/Group/AddGroup.tsx | 2 +- src/components/Group/AddGroupList.tsx | 2 +- src/components/Group/Forum/NewThread.tsx | 2 +- src/components/Group/Group.tsx | 2 +- src/components/Group/InviteMember.tsx | 2 +- src/components/Group/ListOfBans.tsx | 2 +- .../Group/ListOfGroupPromotions.tsx | 2 +- src/components/Group/ListOfInvites.tsx | 2 +- src/components/Group/ListOfJoinRequests.tsx | 2 +- src/components/Group/ListOfMembers.tsx | 2 +- src/components/Group/ManageMembers.tsx | 2 +- src/components/Group/Settings.tsx | 4 +- src/components/Group/UserListOfInvites.tsx | 2 +- src/components/MainAvatar.tsx | 2 +- src/components/Minting/Minting.tsx | 2 +- src/components/NotAuthenticated.tsx | 2 +- src/components/QortPayment.tsx | 2 +- src/components/RegisterName.tsx | 2 +- src/components/Save/Save.tsx | 4 +- src/components/UserLookup.tsx/UserLookup.tsx | 5 +- src/deps/AltcoinHDWallet.ts | 864 ----------------- src/encryption/AltcoinHDWallet.ts | 881 ++++++++++++++++++ src/{deps => encryption}/Base58.ts | 0 .../bcryptworker.worker.js | 0 .../bcryptworkerwasm.worker.js | 0 src/{deps => encryption}/broken-ripemd160.ts | 0 src/{deps => encryption}/ecbn.ts | 0 src/{deps => encryption}/ed2curve.ts | 0 .../encryption.ts | 12 +- src/{deps => encryption}/kdf.ts | 0 src/{deps => encryption}/nacl-fast.ts | 0 src/{deps => encryption}/ripemd160.ts | 0 src/hooks/useHandlePaymentNotification.tsx | 5 +- src/hooks/useHandlePrivateApps.tsx | 4 +- src/hooks/useQortalGetSaveSettings.tsx | 2 +- src/qdn/encryption/group-encryption.ts | 6 +- src/qdn/publish/pubish.ts | 445 ++++----- src/qortalRequests/get.ts | 15 +- src/{ => qortalRequests}/qortalRequests.ts | 10 +- src/transactions/ChatBase.ts | 4 +- src/transactions/ChatTransaction.ts | 4 +- .../RemoveRewardShareTransaction.ts | 2 +- src/transactions/RewardShareTransaction.ts | 4 +- src/transactions/TransactionBase.ts | 4 +- src/transactions/signChat.ts | 2 +- src/transactions/signTradeBotTransaction.ts | 4 +- src/utils/decryptChatMessage.ts | 6 +- src/utils/decryptWallet.ts | 4 +- src/utils/generateWallet/generateWallet.ts | 2 +- src/utils/generateWallet/phrase-wallet.ts | 346 ++++--- .../generateWallet/publicKeyToAddress.ts | 6 +- src/utils/generateWallet/storeWallet.ts | 4 +- src/utils/validateAddress.ts | 2 +- 70 files changed, 1416 insertions(+), 1368 deletions(-) rename src/{ => background}/background-cases.ts (99%) rename src/{ => background}/background.ts (98%) delete mode 100644 src/deps/AltcoinHDWallet.ts create mode 100644 src/encryption/AltcoinHDWallet.ts rename src/{deps => encryption}/Base58.ts (100%) rename src/{deps => encryption}/bcryptworker.worker.js (100%) rename src/{deps => encryption}/bcryptworkerwasm.worker.js (100%) rename src/{deps => encryption}/broken-ripemd160.ts (100%) rename src/{deps => encryption}/ecbn.ts (100%) rename src/{deps => encryption}/ed2curve.ts (100%) rename src/{backgroundFunctions => encryption}/encryption.ts (96%) rename src/{deps => encryption}/kdf.ts (100%) rename src/{deps => encryption}/nacl-fast.ts (100%) rename src/{deps => encryption}/ripemd160.ts (100%) rename src/{ => qortalRequests}/qortalRequests.ts (99%) diff --git a/src/App.tsx b/src/App.tsx index 777454d..fe721a7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -76,7 +76,7 @@ import { groupApi, groupApiSocket, storeWallets, -} from './background'; +} from './background/background.ts'; import { executeEvent, subscribeToEvent, @@ -124,7 +124,7 @@ import { Tutorials } from './components/Tutorials/Tutorials'; import { useHandleTutorials } from './hooks/useHandleTutorials.tsx'; import { useHandleUserInfo } from './hooks/useHandleUserInfo.tsx'; import { Minting } from './components/Minting/Minting'; -import { isRunningGateway } from './qortalRequests'; +import { isRunningGateway } from './qortalRequests/qortalRequests.ts'; import { QMailStatus } from './components/QMailStatus'; import { GlobalActions } from './components/GlobalActions/GlobalActions'; import { useBlockedAddresses } from './hooks/useBlockUsers.tsx'; diff --git a/src/Wallets.tsx b/src/Wallets.tsx index ef259fc..e6e4906 100644 --- a/src/Wallets.tsx +++ b/src/Wallets.tsx @@ -23,7 +23,11 @@ import { useDropzone } from 'react-dropzone'; import EditIcon from '@mui/icons-material/Edit'; import { Label } from './components/Group/AddGroup'; import { Spacer } from './common/Spacer'; -import { getWallets, storeWallets, walletVersion } from './background'; +import { + getWallets, + storeWallets, + walletVersion, +} from './background/background.ts'; import { useModal } from './common/useModal'; import PhraseWallet from './utils/generateWallet/phrase-wallet'; import { decryptStoredWalletFromSeedPhrase } from './utils/decryptWallet'; diff --git a/src/background-cases.ts b/src/background/background-cases.ts similarity index 99% rename from src/background-cases.ts rename to src/background/background-cases.ts index 56697a7..7c6677e 100644 --- a/src/background-cases.ts +++ b/src/background/background-cases.ts @@ -55,20 +55,20 @@ import { setGroupData, updateThreadActivity, walletVersion, -} from './background'; +} from '../background/background.ts'; import { decryptGroupEncryption, encryptAndPublishSymmetricKeyGroupChat, encryptAndPublishSymmetricKeyGroupChatForAdmins, publishGroupEncryptedResource, publishOnQDN, -} from './backgroundFunctions/encryption'; -import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from './constants/constants'; -import Base58 from './deps/Base58'; -import { encryptSingle } from './qdn/encryption/group-encryption'; -import { _createPoll, _voteOnPoll } from './qortalRequests/get'; -import { createTransaction } from './transactions/transactions'; -import { getData, storeData } from './utils/chromeStorage'; +} from '../encryption/encryption.ts'; +import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from '../constants/constants'; +import Base58 from '../encryption/Base58.ts'; +import { encryptSingle } from '../qdn/encryption/group-encryption'; +import { _createPoll, _voteOnPoll } from '../qortalRequests/get'; +import { createTransaction } from '../transactions/transactions'; +import { getData, storeData } from '../utils/chromeStorage'; export function versionCase(request, event) { event.source.postMessage( diff --git a/src/background.ts b/src/background/background.ts similarity index 98% rename from src/background.ts rename to src/background/background.ts index cbc96b3..ec55028 100644 --- a/src/background.ts +++ b/src/background/background.ts @@ -1,27 +1,27 @@ // @ts-nocheck -import './qortalRequests'; +import '../qortalRequests/qortalRequests'; import { isArray } from 'lodash'; -import { uint8ArrayToObject } from './backgroundFunctions/encryption'; -import Base58 from './deps/Base58'; +import { uint8ArrayToObject } from '../encryption/encryption.ts'; +import Base58 from '../encryption/Base58'; import axios from 'axios'; import { base64ToUint8Array, decryptSingle, encryptSingle, objectToBase64, -} from './qdn/encryption/group-encryption'; -import ChatComputePowWorker from './chatComputePow.worker.js?worker'; -import { reusableGet } from './qdn/publish/pubish'; -import { signChat } from './transactions/signChat'; -import { createTransaction } from './transactions/transactions'; -import { decryptChatMessage } from './utils/decryptChatMessage'; -import { decryptStoredWallet } from './utils/decryptWallet'; -import PhraseWallet from './utils/generateWallet/phrase-wallet'; -import { RequestQueueWithPromise } from './utils/queue/queue'; -import { validateAddress } from './utils/validateAddress'; +} from '../qdn/encryption/group-encryption'; +import ChatComputePowWorker from '../chatComputePow.worker.js?worker'; +import { reusableGet } from '../qdn/publish/pubish'; +import { signChat } from '../transactions/signChat'; +import { createTransaction } from '../transactions/transactions'; +import { decryptChatMessage } from '../utils/decryptChatMessage'; +import { decryptStoredWallet } from '../utils/decryptWallet'; +import PhraseWallet from '../utils/generateWallet/phrase-wallet'; +import { RequestQueueWithPromise } from '../utils/queue/queue'; +import { validateAddress } from '../utils/validateAddress'; import { Sha256 } from 'asmcrypto.js'; -import { TradeBotRespondMultipleRequest } from './transactions/TradeBotRespondMultipleRequest'; -import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from './constants/constants'; +import { TradeBotRespondMultipleRequest } from '../transactions/TradeBotRespondMultipleRequest'; +import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from '../constants/constants'; import { addDataPublishesCase, addEnteredQmailTimestampCase, @@ -68,7 +68,6 @@ import { ltcBalanceCase, makeAdminCase, nameCase, - notificationCase, notifyAdminRegenerateSecretKeyCase, pauseAllQueuesCase, publishGroupEncryptedResourceCase, @@ -90,9 +89,13 @@ import { validApiCase, versionCase, voteOnPollCase, -} from './background-cases'; -import { getData, removeKeysAndLogout, storeData } from './utils/chromeStorage'; -import TradeBotRespondRequest from './transactions/TradeBotRespondRequest'; +} from '../background/background-cases'; +import { + getData, + removeKeysAndLogout, + storeData, +} from '../utils/chromeStorage'; +import TradeBotRespondRequest from '../transactions/TradeBotRespondRequest'; export let groupSecretkeys = {}; diff --git a/src/components/Apps/AppPublish.tsx b/src/components/Apps/AppPublish.tsx index 7800c55..abbe754 100644 --- a/src/components/Apps/AppPublish.tsx +++ b/src/components/Apps/AppPublish.tsx @@ -22,7 +22,7 @@ import { executeEvent } from '../../utils/events'; import { useDropzone } from 'react-dropzone'; import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { fileToBase64 } from '../../utils/fileReading'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/Apps/AppRating.tsx b/src/components/Apps/AppRating.tsx index 9d63213..edd628a 100644 --- a/src/components/Apps/AppRating.tsx +++ b/src/components/Apps/AppRating.tsx @@ -1,6 +1,6 @@ import { Box, Rating } from '@mui/material'; import { useCallback, useContext, useEffect, useRef, useState } from 'react'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { StarFilledIcon } from '../../assets/Icons/StarFilled'; diff --git a/src/components/Apps/AppsDevModeHome.tsx b/src/components/Apps/AppsDevModeHome.tsx index d21618b..f9ade5d 100644 --- a/src/components/Apps/AppsDevModeHome.tsx +++ b/src/components/Apps/AppsDevModeHome.tsx @@ -23,7 +23,7 @@ import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { executeEvent } from '../../utils/events'; import { Spacer } from '../../common/Spacer'; import { useModal } from '../../common/useModal'; -import { createEndpoint, isUsingLocal } from '../../background'; +import { createEndpoint, isUsingLocal } from '../../background/background.ts'; import { Label } from '../Group/AddGroup'; import ShortUniqueId from 'short-unique-id'; import swaggerSVG from '../../assets/svgs/swagger.svg'; diff --git a/src/components/Apps/AppsPrivate.tsx b/src/components/Apps/AppsPrivate.tsx index 64d5045..afbf186 100644 --- a/src/components/Apps/AppsPrivate.tsx +++ b/src/components/Apps/AppsPrivate.tsx @@ -34,7 +34,7 @@ import ImageUploader from '../../common/ImageUploader'; import { QORTAL_APP_CONTEXT } from '../../App'; import { fileToBase64 } from '../../utils/fileReading'; import { objectToBase64 } from '../../qdn/encryption/group-encryption'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { useAtom } from 'jotai'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/Chat/AdminSpaceInner.tsx b/src/components/Chat/AdminSpaceInner.tsx index 0a68048..97e3fd1 100644 --- a/src/components/Chat/AdminSpaceInner.tsx +++ b/src/components/Chat/AdminSpaceInner.tsx @@ -10,9 +10,9 @@ import { getPublishesFromAdmins, validateSecretKey, } from '../Group/Group'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { base64ToUint8Array } from '../../qdn/encryption/group-encryption'; -import { uint8ArrayToObject } from '../../backgroundFunctions/encryption'; +import { uint8ArrayToObject } from '../../encryption/encryption.ts'; import { formatTimestampForum } from '../../utils/time'; import { Spacer } from '../../common/Spacer'; import { GroupAvatar } from './GroupAvatar'; diff --git a/src/components/Chat/AnnouncementDiscussion.tsx b/src/components/Chat/AnnouncementDiscussion.tsx index 8959a9f..e0da144 100644 --- a/src/components/Chat/AnnouncementDiscussion.tsx +++ b/src/components/Chat/AnnouncementDiscussion.tsx @@ -8,7 +8,7 @@ import { Box, CircularProgress, useTheme } from '@mui/material'; import { objectToBase64 } from '../../qdn/encryption/group-encryption'; import ShortUniqueId from 'short-unique-id'; import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { decryptPublishes, getTempPublish, diff --git a/src/components/Chat/ChatDirect.tsx b/src/components/Chat/ChatDirect.tsx index 35370ad..36681e9 100644 --- a/src/components/Chat/ChatDirect.tsx +++ b/src/components/Chat/ChatDirect.tsx @@ -14,7 +14,7 @@ import { pauseAllQueues, resumeAllQueues, } from '../../App'; -import { getPublicKey } from '../../background'; +import { getPublicKey } from '../../background/background.ts'; import { useMessageQueue } from '../../MessageQueueContext'; import { executeEvent, diff --git a/src/components/Chat/ChatGroup.tsx b/src/components/Chat/ChatGroup.tsx index fcd6bbd..783fec9 100644 --- a/src/components/Chat/ChatGroup.tsx +++ b/src/components/Chat/ChatGroup.tsx @@ -44,7 +44,7 @@ import ShortUniqueId from 'short-unique-id'; import { ReplyPreview } from './MessageItem'; import { ExitIcon } from '../../assets/Icons/ExitIcon'; import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from '../../constants/constants'; -import { getFee, isExtMsg } from '../../background'; +import { getFee, isExtMsg } from '../../background/background.ts'; import AppViewerContainer from '../Apps/AppViewerContainer'; import CloseIcon from '@mui/icons-material/Close'; import { throttle } from 'lodash'; diff --git a/src/components/Chat/CreateCommonSecret.tsx b/src/components/Chat/CreateCommonSecret.tsx index e7472f6..46f76d6 100644 --- a/src/components/Chat/CreateCommonSecret.tsx +++ b/src/components/Chat/CreateCommonSecret.tsx @@ -8,14 +8,14 @@ import { getBaseApiReact, pauseAllQueues, } from '../../App'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { decryptResource, getGroupAdmins, validateSecretKey, } from '../Group/Group'; import { base64ToUint8Array } from '../../qdn/encryption/group-encryption'; -import { uint8ArrayToObject } from '../../backgroundFunctions/encryption'; +import { uint8ArrayToObject } from '../../encryption/encryption.ts'; import { useSetAtom } from 'jotai'; import { txListAtom } from '../../atoms/global'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/Chat/GroupAnnouncements.tsx b/src/components/Chat/GroupAnnouncements.tsx index a9d1e0a..18170cd 100644 --- a/src/components/Chat/GroupAnnouncements.tsx +++ b/src/components/Chat/GroupAnnouncements.tsx @@ -7,7 +7,7 @@ import { useRef, useState, } from 'react'; -import { uint8ArrayToObject } from '../../backgroundFunctions/encryption'; +import { uint8ArrayToObject } from '../../encryption/encryption.ts'; import { base64ToUint8Array, objectToBase64, @@ -15,7 +15,7 @@ import { import Tiptap from './TipTap'; import { CustomButton } from '../../styles/App-styles'; import CircularProgress from '@mui/material/CircularProgress'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; import { Box, Typography, useTheme } from '@mui/material'; import { Spacer } from '../../common/Spacer'; diff --git a/src/components/Chat/GroupAvatar.tsx b/src/components/Chat/GroupAvatar.tsx index 5c28899..f62a241 100644 --- a/src/components/Chat/GroupAvatar.tsx +++ b/src/components/Chat/GroupAvatar.tsx @@ -16,7 +16,7 @@ import { } from '@mui/material'; import { Spacer } from '../../common/Spacer'; import ImageUploader from '../../common/ImageUploader'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { fileToBase64 } from '../../utils/fileReading'; import { LoadingButton } from '@mui/lab'; import ErrorIcon from '@mui/icons-material/Error'; diff --git a/src/components/Embeds/PollEmbed.tsx b/src/components/Embeds/PollEmbed.tsx index bc246ba..6d7a510 100644 --- a/src/components/Embeds/PollEmbed.tsx +++ b/src/components/Embeds/PollEmbed.tsx @@ -16,7 +16,7 @@ import { } from '@mui/material'; import { getNameInfo } from '../Group/Group'; import PollIcon from '@mui/icons-material/Poll'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import RefreshIcon from '@mui/icons-material/Refresh'; import { Spacer } from '../../common/Spacer'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; diff --git a/src/components/GlobalActions/JoinGroup.tsx b/src/components/GlobalActions/JoinGroup.tsx index 48ea442..95e95e6 100644 --- a/src/components/GlobalActions/JoinGroup.tsx +++ b/src/components/GlobalActions/JoinGroup.tsx @@ -12,7 +12,7 @@ import { } from '@mui/material'; import { CustomButtonAccept } from '../../styles/App-styles'; import { getBaseApiReact, QORTAL_APP_CONTEXT } from '../../App'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { FidgetSpinner } from 'react-loader-spinner'; import { useAtom, useSetAtom } from 'jotai'; diff --git a/src/components/Group/AddGroup.tsx b/src/components/Group/AddGroup.tsx index 2e0ca92..ec01443 100644 --- a/src/components/Group/AddGroup.tsx +++ b/src/components/Group/AddGroup.tsx @@ -34,7 +34,7 @@ import { import { AddGroupList } from './AddGroupList'; import { UserListOfInvites } from './UserListOfInvites'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { QORTAL_APP_CONTEXT } from '../../App'; import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/Group/AddGroupList.tsx b/src/components/Group/AddGroupList.tsx index e9316d8..8cad787 100644 --- a/src/components/Group/AddGroupList.tsx +++ b/src/components/Group/AddGroupList.tsx @@ -25,7 +25,7 @@ import { import _ from 'lodash'; import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { LoadingButton } from '@mui/lab'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import LockIcon from '@mui/icons-material/Lock'; import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred'; import { Spacer } from '../../common/Spacer'; diff --git a/src/components/Group/Forum/NewThread.tsx b/src/components/Group/Forum/NewThread.tsx index b9d8048..9749480 100644 --- a/src/components/Group/Forum/NewThread.tsx +++ b/src/components/Group/Forum/NewThread.tsx @@ -22,7 +22,7 @@ import { pauseAllQueues, resumeAllQueues, } from '../../../App'; -import { getFee } from '../../../background'; +import { getFee } from '../../../background/background'; import TipTap from '../../Chat/TipTap'; import { MessageDisplay } from '../../Chat/MessageDisplay'; import { CustomizedSnackbars } from '../../Snackbar/Snackbar'; diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx index 8283fce..469a86f 100644 --- a/src/components/Group/Group.tsx +++ b/src/components/Group/Group.tsx @@ -13,7 +13,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { ChatGroup } from '../Chat/ChatGroup'; import { CreateCommonSecret } from '../Chat/CreateCommonSecret'; import { base64ToUint8Array } from '../../qdn/encryption/group-encryption'; -import { uint8ArrayToObject } from '../../backgroundFunctions/encryption'; +import { uint8ArrayToObject } from '../../encryption/encryption'; import { AddGroup } from './AddGroup'; import CreateIcon from '@mui/icons-material/Create'; import { diff --git a/src/components/Group/InviteMember.tsx b/src/components/Group/InviteMember.tsx index 07ee3a7..8a562d0 100644 --- a/src/components/Group/InviteMember.tsx +++ b/src/components/Group/InviteMember.tsx @@ -3,7 +3,7 @@ import { Box, Input, MenuItem, Select, SelectChangeEvent } from '@mui/material'; import { useState } from 'react'; import { Spacer } from '../../common/Spacer'; import { Label } from './AddGroup'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { useTranslation } from 'react-i18next'; export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => { diff --git a/src/components/Group/ListOfBans.tsx b/src/components/Group/ListOfBans.tsx index 9399b7d..122ce27 100644 --- a/src/components/Group/ListOfBans.tsx +++ b/src/components/Group/ListOfBans.tsx @@ -15,7 +15,7 @@ import { List, } from 'react-virtualized'; import { getNameInfo } from './Group'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { LoadingButton } from '@mui/lab'; import { getBaseApiReact } from '../../App'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/Group/ListOfGroupPromotions.tsx b/src/components/Group/ListOfGroupPromotions.tsx index 8c772cd..b3bedab 100644 --- a/src/components/Group/ListOfGroupPromotions.tsx +++ b/src/components/Group/ListOfGroupPromotions.tsx @@ -42,7 +42,7 @@ import { useVirtualizer } from '@tanstack/react-virtual'; import ErrorBoundary from '../../common/ErrorBoundary'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandLessIcon from '@mui/icons-material/ExpandLess'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { useAtom, useSetAtom } from 'jotai'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/Group/ListOfInvites.tsx b/src/components/Group/ListOfInvites.tsx index 8c4fe9b..037dddc 100644 --- a/src/components/Group/ListOfInvites.tsx +++ b/src/components/Group/ListOfInvites.tsx @@ -15,7 +15,7 @@ import { List, } from 'react-virtualized'; import { getNameInfo } from './Group'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { LoadingButton } from '@mui/lab'; import { getBaseApiReact } from '../../App'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/Group/ListOfJoinRequests.tsx b/src/components/Group/ListOfJoinRequests.tsx index 1805733..5fbfe9e 100644 --- a/src/components/Group/ListOfJoinRequests.tsx +++ b/src/components/Group/ListOfJoinRequests.tsx @@ -15,7 +15,7 @@ import { List, } from 'react-virtualized'; import { getNameInfo } from './Group'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { LoadingButton } from '@mui/lab'; import { getBaseApiReact } from '../../App'; import { txListAtom } from '../../atoms/global'; diff --git a/src/components/Group/ListOfMembers.tsx b/src/components/Group/ListOfMembers.tsx index 7a0048f..c57e501 100644 --- a/src/components/Group/ListOfMembers.tsx +++ b/src/components/Group/ListOfMembers.tsx @@ -17,7 +17,7 @@ import { List, } from 'react-virtualized'; import { LoadingButton } from '@mui/lab'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { getBaseApiReact } from '../../App'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/Group/ManageMembers.tsx b/src/components/Group/ManageMembers.tsx index 9fd3150..e87b002 100644 --- a/src/components/Group/ManageMembers.tsx +++ b/src/components/Group/ManageMembers.tsx @@ -28,7 +28,7 @@ import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { getGroupMembers, getNames } from './Group'; import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { LoadingButton } from '@mui/lab'; import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; import { Spacer } from '../../common/Spacer'; diff --git a/src/components/Group/Settings.tsx b/src/components/Group/Settings.tsx index 058adde..78db9bc 100644 --- a/src/components/Group/Settings.tsx +++ b/src/components/Group/Settings.tsx @@ -36,8 +36,8 @@ import { useAtom } from 'jotai'; import { decryptStoredWallet } from '../../utils/decryptWallet'; import { Spacer } from '../../common/Spacer'; import PhraseWallet from '../../utils/generateWallet/phrase-wallet'; -import { walletVersion } from '../../background'; -import Base58 from '../../deps/Base58'; +import { walletVersion } from '../../background/background.ts'; +import Base58 from '../../encryption/Base58.ts'; import { QORTAL_APP_CONTEXT } from '../../App'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/Group/UserListOfInvites.tsx b/src/components/Group/UserListOfInvites.tsx index 064296a..2717af9 100644 --- a/src/components/Group/UserListOfInvites.tsx +++ b/src/components/Group/UserListOfInvites.tsx @@ -16,7 +16,7 @@ import { } from 'react-virtualized'; import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { LoadingButton } from '@mui/lab'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import LockIcon from '@mui/icons-material/Lock'; import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred'; import { Spacer } from '../../common/Spacer'; diff --git a/src/components/MainAvatar.tsx b/src/components/MainAvatar.tsx index 26ce2e1..8e03e97 100644 --- a/src/components/MainAvatar.tsx +++ b/src/components/MainAvatar.tsx @@ -16,7 +16,7 @@ import { } from '@mui/material'; import { Spacer } from '../common/Spacer'; import ImageUploader from '../common/ImageUploader'; -import { getFee } from '../background'; +import { getFee } from '../background/background.ts'; import { fileToBase64 } from '../utils/fileReading'; import { LoadingButton } from '@mui/lab'; import ErrorIcon from '@mui/icons-material/Error'; diff --git a/src/components/Minting/Minting.tsx b/src/components/Minting/Minting.tsx index 114d521..2c2d877 100644 --- a/src/components/Minting/Minting.tsx +++ b/src/components/Minting/Minting.tsx @@ -21,7 +21,7 @@ import { subscribeToEvent, unsubscribeFromEvent, } from '../../utils/events'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { Spacer } from '../../common/Spacer'; import { FidgetSpinner } from 'react-loader-spinner'; import { useModal } from '../../common/useModal'; diff --git a/src/components/NotAuthenticated.tsx b/src/components/NotAuthenticated.tsx index c9be445..0d2949c 100644 --- a/src/components/NotAuthenticated.tsx +++ b/src/components/NotAuthenticated.tsx @@ -27,7 +27,7 @@ import { import Logo1Dark from '../assets/svgs/Logo1Dark.svg'; import HelpIcon from '@mui/icons-material/Help'; import { CustomizedSnackbars } from './Snackbar/Snackbar'; -import { cleanUrl, gateways } from '../background'; +import { cleanUrl, gateways } from '../background/background.ts'; import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip'; import ThemeSelector from './Theme/ThemeSelector'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/QortPayment.tsx b/src/components/QortPayment.tsx index c031914..1907686 100644 --- a/src/components/QortPayment.tsx +++ b/src/components/QortPayment.tsx @@ -2,7 +2,7 @@ import { Box, useTheme } from '@mui/material'; import { useState } from 'react'; import { TextP } from '../styles/App-styles'; import { Spacer } from '../common/Spacer'; -import { getFee } from '../background'; +import { getFee } from '../background/background.ts'; import { useTranslation } from 'react-i18next'; export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => { diff --git a/src/components/RegisterName.tsx b/src/components/RegisterName.tsx index 6e2bfe3..2b5b224 100644 --- a/src/components/RegisterName.tsx +++ b/src/components/RegisterName.tsx @@ -17,7 +17,7 @@ import { import { Label } from './Group/AddGroup'; import { Spacer } from '../common/Spacer'; import { getBaseApiReact } from '../App'; -import { getFee } from '../background'; +import { getFee } from '../background/background.ts'; import RadioButtonCheckedIcon from '@mui/icons-material/RadioButtonChecked'; import { subscribeToEvent, unsubscribeFromEvent } from '../utils/events'; import { BarSpinner } from '../common/Spinners/BarSpinner/BarSpinner'; diff --git a/src/components/Save/Save.tsx b/src/components/Save/Save.tsx index dd9c148..75ed603 100644 --- a/src/components/Save/Save.tsx +++ b/src/components/Save/Save.tsx @@ -19,7 +19,7 @@ import { } from '@mui/material'; import { objectToBase64 } from '../../qdn/encryption/group-encryption'; import { QORTAL_APP_CONTEXT } from '../../App'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { SaveIcon } from '../../assets/Icons/SaveIcon'; import { IconWrapper } from '../Desktop/DesktopFooter'; @@ -31,7 +31,7 @@ import { saveFileToDiskGeneric } from '../../utils/generateWallet/generateWallet import { base64ToUint8Array, uint8ArrayToObject, -} from '../../backgroundFunctions/encryption'; +} from '../../encryption/encryption.ts'; import { useTranslation } from 'react-i18next'; import { useAtom, useSetAtom } from 'jotai'; diff --git a/src/components/UserLookup.tsx/UserLookup.tsx b/src/components/UserLookup.tsx/UserLookup.tsx index 675d661..3be4ef6 100644 --- a/src/components/UserLookup.tsx/UserLookup.tsx +++ b/src/components/UserLookup.tsx/UserLookup.tsx @@ -19,7 +19,10 @@ import { useTheme, Autocomplete, } from '@mui/material'; -import { getAddressInfo, getNameOrAddress } from '../../background'; +import { + getAddressInfo, + getNameOrAddress, +} from '../../background/background.ts'; import { getBaseApiReact } from '../../App'; import { getNameInfo } from '../Group/Group'; import AccountCircleIcon from '@mui/icons-material/AccountCircle'; diff --git a/src/deps/AltcoinHDWallet.ts b/src/deps/AltcoinHDWallet.ts deleted file mode 100644 index cd4a10b..0000000 --- a/src/deps/AltcoinHDWallet.ts +++ /dev/null @@ -1,864 +0,0 @@ -// @ts-nocheck -; -import Base58 from '../deps/Base58.js' -import {Sha256, Sha512} from 'asmcrypto.js' -import jsSHA from 'jssha' -import RIPEMD160 from '../deps/ripemd160.js' -import utils from '../utils/utils' -import {BigInteger, EllipticCurve} from './ecbn' -import {Buffer} from 'buffer' - -export default class AltcoinHDWallet { - - constructor(addressParams) { - - /** - * Seed - 32 bytes - */ - - this.seed = new Uint8Array(32) - - /** - * Version Bytes - 4 byte - */ - - this.versionBytes = addressParams - - /** - * Depth - 1 byte - */ - - this.depth = 0 - - /** - * Parent Fingerprint - 4 bytes - */ - - this.parentFingerprint = '0x00000000' // master key - - /** - * Child Index - 4 bytes - */ - - this.childIndex = '0x00000000' // master key - - /** - * Chain Code - 32 bytes - */ - - this.chainCode = new Uint8Array(32) - - /** - * Key Data - 33 bytes - */ - - this.keyData = new Uint8Array(33) - - /** - * Seed Hash - 64 bytes - */ - - this.seedHash = new Uint8Array(64) - - /** - * Private Key - 32 bytes - */ - - this.privateKey = new Uint8Array(32) - - /** - * Public Key - 33 bytes (compressed) - */ - - this.publicKey = new Uint8Array(33) - - /** - * Public Key Hash160 (used to derive the parent fingerprint for derived) - */ - - this.publicKeyHash = new Uint8Array(20) - - /** - * Master Private Key (Base58 encoded) - */ - - this.masterPrivateKey = '' - - /** - * Master Public Key (Base58 encoded) - */ - - this.masterPublicKey = '' - - /** - * Testnet Master Private Key (Base58 encoded) - THIS IS TESTNET - */ - - this._tMasterPrivateKey = '' - - /** - * Testnet Master Public Key (Base58 encoded) - THIS IS TESTNET - */ - - this._tmasterPublicKey = '' - - /** - * Child Keys Derivation from the Parent Keys - */ - - /** - * Child Private Key - 32 bytes - */ - - this.childPrivateKey = new Uint8Array(32) - - /** - * Child Chain Code - 32 bytes - */ - - this.childChainCode = new Uint8Array(32) - - /** - * Child Public Key - 33 bytes (compressed) - */ - - this.childPublicKey = new Uint8Array(33) - - /** - * Child Public Key Hash160 (used to derive the parent fingerprint for derived) - */ - - this.childPublicKeyHash = new Uint8Array(20) - - /** - * Extended Private Child Key - Base58 encoded - */ - - this.xPrivateChildKey = '' - - /** - * Extended Public Child Key - Base58 encoded - */ - - this.xPublicChildKey = '' - - /** - * Grand Child Keys Derivation from the Child Keys - */ - - /** - * Grand Child Private Key - 32 bytes - */ - - this.grandChildPrivateKey = new Uint8Array(32) - - /** - * Grand Child Chain Code - 32 bytes - */ - - this.grandChildChainCode = new Uint8Array(32) - - /** - * Grand Child Public Key - 33 bytes (compressed) - */ - - this.grandChildPublicKey = new Uint8Array(33) - - /** - * Grand Public Key Hash160 (used to derive the parent fingerprint for derived) - */ - - this.grandChildPublicKeyHash = new Uint8Array(20) - - /** - * Extended Private Grand Child Key - Base58 encoded - */ - - this.xPrivateGrandChildKey = '' - - /** - * Extended Public Grand Child Key - Base58 encoded - */ - - this.xPublicGrandChildKey = '' - - /** - * Litecoin Legacy Address - Derived from the Grand Child Public Key Hash - */ - - this.litecoinLegacyAddress = '' - - /** - * TESTNET Litecoin Legacy Address (Derived from the Grand Child Public Key Hash) - THIS IS TESTNET - */ - - this._tlitecoinLegacyAddress = '' - - /** - * Wallet - Wallet Object (keys...) - */ - - this.wallet = {} - } - - setSeed(seed) { - this.seed = seed - } - - createWallet(seed, isBIP44, indicator = null) { - - // Set Seeed - this.setSeed(seed) - - // Generate Seed Hash - this.generateSeedHash(this.seed, isBIP44, indicator) - - // Generate Private Key - this.generatePrivateKey(this.seedHash) - - // Generate Chain Code - this.generateChainCode(this.seedHash) - - // Generate Public Key from Private Key - this.generatePublicKey(this.privateKey) - - // Generate Mainnet Master Private Key - this.generateMainnetMasterPrivateKey() - - // Generate Mainnet Master Public Key - this.generateMainnetMasterPublicKey() - - // Generate Testnet Master Private Key - this.generateTestnetMasterPrivateKey() - - // Generate Testnet Master Public Key - this.generateTestnetMasterPublicKey() - - // Generate Child and Grand Child Keys - this.generateDerivedChildKeys() - - // Return Wallet Object Specification - return this.returnWallet() - } - - - generateSeedHash(seed, isBIP44, indicator = null) { - let buffer - - if (isBIP44) { - buffer = utils.appendBuffer(seed.reverse(), utils.int32ToBytes(indicator)) - } else { - if(indicator !== null) { - const indicatorString = utils.stringtoUTF8Array(indicator) - buffer = utils.appendBuffer(seed.reverse(), indicatorString) - } - else - { - buffer = seed.reverse() - } - } - - const _reverseSeedHash = new Sha256().process(buffer).finish().result - this.seedHash = new Sha512().process(utils.appendBuffer(seed, _reverseSeedHash)).finish().result - } - - generatePrivateKey(seedHash) { - const SECP256K1_CURVE_ORDER = new BigInteger("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141") - - const privateKeyHash = seedHash.slice(0, 32) - - this.seed58 = Base58.encode(privateKeyHash) - - const _privateKeyHash = [...privateKeyHash] - let privateKeyBigInt = BigInteger.fromByteArrayUnsigned(_privateKeyHash) - - const privateKey = (privateKeyBigInt.mod(SECP256K1_CURVE_ORDER.subtract(BigInteger.ONE))).add(BigInteger.ONE) - this.privateKey = privateKey.toByteArrayUnsigned() - } - - generateChainCode(seedHash) { - this.chainCode = new Sha256().process(seedHash.slice(32, 64)).finish().result - } - - generatePublicKey(privateKey) { - const _privateKey = [...privateKey] - const privateKeyBigInt = BigInteger.fromByteArrayUnsigned(_privateKey) - - const epCurve = EllipticCurve.getSECCurveByName("secp256k1") - const curvePoints = epCurve.getG().multiply(privateKeyBigInt) - - - const x = curvePoints.getX().toBigInteger() - const y = curvePoints.getY().toBigInteger() - - /** - * Deriving Uncompressed Public Key (65 bytes) - * - * const publicKeyBytes = EllipticCurve.integerToBytes(x, 32) - * this.publicKey = publicKeyBytes.concat(EllipticCurve.integerToBytes(y, 32)) - * this.publicKey.unshift(0x04) // append point indicator - */ - - // Compressed Public Key (33 bytes) - this.publicKey = EllipticCurve.integerToBytes(x, 32) - - - if (y.isEven()) { - this.publicKey.unshift(0x02) // append point indicator - } else { - this.publicKey.unshift(0x03) // append point indicator - } - - // PublicKey Hash - const publicKeySHA256 = new Sha256().process(new Uint8Array(this.publicKey)).finish().result - const _publicKeyHash = new RIPEMD160().update(Buffer.from(publicKeySHA256)).digest('hex') - this.publicKeyHash = _publicKeyHash - } - - generateMainnetMasterPrivateKey() { - // Serialization Variable - const s = [] - - // Append Version Byte - s.push(...(utils.int32ToBytes(this.versionBytes.mainnet.private))) - - // Append Depth - s.push(this.depth) - - // Append Parent Fingerprint - s.push(...(utils.int32ToBytes(this.parentFingerprint))) - - // Append Child Number - s.push(...(utils.int32ToBytes(this.childIndex))) - - // Append Chain Code - s.push(...this.chainCode) - - // Append 1 byte '0x00' (to make the key data 33 bytes, DO THIS ONLY FOR PRIVATE KEYS ) - s.push(0) - - //if the private key length is less than 32 let's add leading zeros - if(this.privateKey.length<32){ - for(let i=this.privateKey.length;i<32;i++){ - s.push(0) - } - } - - // Append Private Key - s.push(...this.privateKey) - - // Generate CheckSum - const _s = new Uint8Array(s) - const _checkSum = new Sha256().process(new Sha256().process(_s).finish().result).finish().result - const checkSum = _checkSum.slice(0, 4) - - // Append CheckSum - s.push(...checkSum) // And this brings us to the end of the serialization... - - // Save to Private Key as Base58 encoded - this.masterPrivateKey = Base58.encode(s) - } - - generateMainnetMasterPublicKey() { - // Serialization Variable - const s = [] - - // Append Version Byte - s.push(...(utils.int32ToBytes(this.versionBytes.mainnet.public))) - - // Append Depth - s.push(this.depth) - - // Append Parent Fingerprint - s.push(...(utils.int32ToBytes(this.parentFingerprint))) - - // Append Child Number - s.push(...(utils.int32ToBytes(this.childIndex))) - - // Append Chain Code - s.push(...this.chainCode) - - // Append Public Key - s.push(...this.publicKey) - - // Generate CheckSum - const _s = new Uint8Array(s) - const _checkSum = new Sha256().process(new Sha256().process(_s).finish().result).finish().result - const checkSum = _checkSum.slice(0, 4) - - // Append CheckSum - s.push(...checkSum) // And this brings us to the end of the serialization... - - // Save to Public Key as Base58 encoded - this.masterPublicKey = Base58.encode(s) - } - - generateTestnetMasterPrivateKey() { - - // To be Used ONLY in Testnet... - - // Serialization Variable - const s = [] - - // Append Version Byte - s.push(...(utils.int32ToBytes(this.versionBytes.testnet.private))) - - // Append Depth - s.push(this.depth) - - // Append Parent Fingerprint - s.push(...(utils.int32ToBytes(this.parentFingerprint))) - - // Append Child Number - s.push(...(utils.int32ToBytes(this.childIndex))) - - // Append Chain Code - s.push(...this.chainCode) - - // Append 1 byte '0x00' (to make the key data 33 bytes, DO THIS ONLY FOR PRIVATE KEYS ) - s.push(0) - - // Append Private Key - s.push(...this.privateKey) - - // Generate CheckSum - const _s = new Uint8Array(s) - const _checkSum = new Sha256().process(new Sha256().process(_s).finish().result).finish().result - const checkSum = _checkSum.slice(0, 4) - - // Append CheckSum - s.push(...checkSum) // And this brings us to the end of the serialization... - - // Save to Private Key as Base58 encoded - this._tMasterPrivateKey = Base58.encode(s) - } - - generateTestnetMasterPublicKey() { - - // To be Used ONLY in Testnet... - - // Serialization Variable - const s = [] - - // Append Version Byte - s.push(...(utils.int32ToBytes(this.versionBytes.testnet.public))) - - // Append Depth - s.push(this.depth) - - // Append Parent Fingerprint - s.push(...(utils.int32ToBytes(this.parentFingerprint))) - - // Append Child Number - s.push(...(utils.int32ToBytes(this.childIndex))) - - // Append Chain Code - s.push(...this.chainCode) - - // Append Private Key - s.push(...this.publicKey) - - // Generate CheckSum - const _s = new Uint8Array(s) - const _checkSum = new Sha256().process(new Sha256().process(_s).finish().result).finish().result - const checkSum = _checkSum.slice(0, 4) - - // Append CheckSum - s.push(...checkSum) // And this brings us to the end of the serialization... - - // Save to Private Key as Base58 encoded - this._tmasterPublicKey = Base58.encode(s) - } - - generateDerivedChildKeys() { - - // SPEC INFO: https://en.bitcoin.it/wiki/BIP_0032#Child_key_derivation_.28CKD.29_functions - // NOTE: will not be using some of derivations func as the value is known. (So I'd rather shove in the values and rewrite out the derivations later ?) - - // NOTE: I "re-wrote" and "reduplicate" the code for child and grandChild keys derivations inorder to get the child and grandchild from the child - // TODO: Make this more better in the future - - const path = 'm/0/0' - // let p = path.split('/') - - // Get Public kEY - const derivePublicChildKey = () => { - - const _privateKey = [...this.childPrivateKey] - const privateKeyBigInt = BigInteger.fromByteArrayUnsigned(_privateKey) - - const epCurve = EllipticCurve.getSECCurveByName("secp256k1") - const curvePoints = epCurve.getG().multiply(privateKeyBigInt) - - const x = curvePoints.getX().toBigInteger() - const y = curvePoints.getY().toBigInteger() - - // Compressed Public Key (33 bytes) - this.childPublicKey = EllipticCurve.integerToBytes(x, 32) - - - if (y.isEven()) { - - this.childPublicKey.unshift(0x02) // append point indicator - } else { - - this.childPublicKey.unshift(0x03) // append point indicator - } - - // PublicKey Hash - const childPublicKeySHA256 = new Sha256().process(new Uint8Array(this.childPublicKey)).finish().result - const _childPublicKeyHash = new RIPEMD160().update(Buffer.from(childPublicKeySHA256)).digest('hex') - this.childPublicKeyHash = _childPublicKeyHash - - - // Call deriveExtendedPublicChildKey // WIll be hardcoding the values... - deriveExtendedPublicChildKey(1, 0) - } - - const derivePrivateChildKey = (cI) => { - - let ib = [] - ib.push((cI >> 24) & 0xff) - ib.push((cI >> 16) & 0xff) - ib.push((cI >> 8) & 0xff) - ib.push(cI & 0xff) - - const s = [...this.publicKey].concat(ib) - - const _hmacSha512 = new jsSHA("SHA-512", "UINT8ARRAY", { numRounds: 1, hmacKey: { value: this.chainCode, format: "UINT8ARRAY" } }) - _hmacSha512.update(new Uint8Array(s)) - - - const IL = BigInteger.fromByteArrayUnsigned([..._hmacSha512.getHMAC('UINT8ARRAY').slice(0, 32)]) - this.childChainCode = _hmacSha512.getHMAC('UINT8ARRAY').slice(32, 64) // IR according to the SPEC - - - // SECP256k1 init - const epCurve = EllipticCurve.getSECCurveByName("secp256k1") - - - const ki = IL.add(BigInteger.fromByteArrayUnsigned(this.privateKey)).mod(epCurve.getN()) // parse256(IL) + kpar (mod n) ==> ki - this.childPrivateKey = ki.toByteArrayUnsigned() - - // Call deriveExtendedPrivateChildKey - deriveExtendedPrivateChildKey(1, 0) - } - - - const deriveExtendedPrivateChildKey = (i, childIndex) => { - - // Serialization Variable - const s = [] - - // Append Version Byte - s.push(...(utils.int32ToBytes(this.versionBytes.mainnet.private))) - - // Append Depth (using the index as depth) - i = parseInt(i) - s.push(i) - - // Append Parent Fingerprint - s.push(...(this.publicKeyHash.slice(0, 4))) - - // Append Child Index - s.push(childIndex >>> 24) - s.push((childIndex >>> 16) & 0xff) - s.push((childIndex >>> 8) & 0xff) - s.push(childIndex & 0xff) - - // Append Chain Code - s.push(...this.childChainCode) - - // Append 1 byte '0x00' (to make the key data 33 bytes, DO THIS ONLY FOR PRIVATE KEYS ) - s.push(0) - - // Append Private Key - s.push(...this.childPrivateKey) - - // Generate CheckSum - const _s = new Uint8Array(s) - const _checkSum = new Sha256().process(new Sha256().process(_s).finish().result).finish().result - const checkSum = _checkSum.slice(0, 4) - - // Append CheckSum - s.push(...checkSum) // And this brings us to the end of the serialization... - - // Save to Private Key as Base58 encoded - this.xPrivateChildKey = Base58.encode(s) - } - - const deriveExtendedPublicChildKey = (i, childIndex) => { - - // Serialization Variable - const s = [] - - // Append Version Byte - s.push(...(utils.int32ToBytes(this.versionBytes.mainnet.public))) - - // Append Depth - i = parseInt(i) - s.push(i) - - // Append Parent Fingerprint - s.push(...(this.publicKeyHash.slice(0, 4))) - - // Append Child Index - s.push(childIndex >>> 24) - s.push((childIndex >>> 16) & 0xff) - s.push((childIndex >>> 8) & 0xff) - s.push(childIndex & 0xff) - - // Append Chain Code - s.push(...this.childChainCode) - - // Append Public Key - s.push(...this.childPublicKey) - - // Generate CheckSum - const _s = new Uint8Array(s) - const _checkSum = new Sha256().process(new Sha256().process(_s).finish().result).finish().result - const checkSum = _checkSum.slice(0, 4) - - // Append CheckSum - s.push(...checkSum) // And this brings us to the end of the serialization... - - - // Save to Public Key as Base58 encoded - this.xPublicChildKey = Base58.encode(s) - } - - - /** - * GRAND CHILD KEYS - * - * NOTE: I know this is not the best way to generate this (even though it works the way it ought) - * Things to rewrite will be and not limited to deriving this through a for loop, removing hard code values, etc... - */ - - const derivePublicGrandChildKey = () => { - - const _privateKey = [...this.grandChildPrivateKey] - const privateKeyBigInt = BigInteger.fromByteArrayUnsigned(_privateKey) - - - const epCurve = EllipticCurve.getSECCurveByName("secp256k1") - const curvePoints = epCurve.getG().multiply(privateKeyBigInt) - - const x = curvePoints.getX().toBigInteger() - const y = curvePoints.getY().toBigInteger() - - // Compressed Public Key (33 bytes) - this.grandChildPublicKey = EllipticCurve.integerToBytes(x, 32) - - - if (y.isEven()) { - this.grandChildPublicKey.unshift(0x02) // append point indicator - } else { - this.grandChildPublicKey.unshift(0x03) // append point indicator - } - - - // PublicKey Hash - const grandChildPublicKeySHA256 = new Sha256().process(new Uint8Array(this.grandChildPublicKey)).finish().result - const _grandChildPublicKeyHash = new RIPEMD160().update(Buffer.from(grandChildPublicKeySHA256)).digest('hex') - this.grandChildPublicKeyHash = _grandChildPublicKeyHash - - - // Call deriveExtendedPublicChildKey // WIll be hardcoding the values... - deriveExtendedPublicGrandChildKey(2, 0) - - /** - * Derive Litecoin Legacy Address - */ - - // Append Address Prefix - let prefix = [this.versionBytes.mainnet.prefix] - if (2 == this.versionBytes.mainnet.prefix.length) { - prefix = [this.versionBytes.mainnet.prefix[0]] - prefix.push([this.versionBytes.mainnet.prefix[1]]) - } - - const k = prefix.concat(...this.grandChildPublicKeyHash) - - // Derive Checksum - const _addressCheckSum = new Sha256().process(new Sha256().process(new Uint8Array(k)).finish().result).finish().result - const addressCheckSum = _addressCheckSum.slice(0, 4) - - // Append CheckSum - const _litecoinLegacyAddress = k.concat(...addressCheckSum) - - // Convert to Base58 - this.litecoinLegacyAddress = Base58.encode(_litecoinLegacyAddress) - - - /** - * Derive TESTNET Litecoin Legacy Address - */ - - // Append Version Byte - const tK = [this.versionBytes.testnet.prefix].concat(...this.grandChildPublicKeyHash) - - // Derive Checksum - const _tAddressCheckSum = new Sha256().process(new Sha256().process(new Uint8Array(tK)).finish().result).finish().result - const tAddressCheckSum = _tAddressCheckSum.slice(0, 4) - - // Append CheckSum - const _tlitecoinLegacyAddress = tK.concat(...tAddressCheckSum) - - // Convert to Base58 - this._tlitecoinLegacyAddress = Base58.encode(_tlitecoinLegacyAddress) - } - - const derivePrivateGrandChildKey = (cI, i) => { - - let ib = [] - ib.push((cI >> 24) & 0xff) - ib.push((cI >> 16) & 0xff) - ib.push((cI >> 8) & 0xff) - ib.push(cI & 0xff) - - const s = [...this.childPublicKey].concat(ib) - - - const _hmacSha512 = new jsSHA("SHA-512", "UINT8ARRAY", { numRounds: 1, hmacKey: { value: this.childChainCode, format: "UINT8ARRAY" } }) - _hmacSha512.update(new Uint8Array(s)) - - - const IL = BigInteger.fromByteArrayUnsigned([..._hmacSha512.getHMAC('UINT8ARRAY').slice(0, 32)]) - this.grandChildChainCode = _hmacSha512.getHMAC('UINT8ARRAY').slice(32, 64) // IR according to the SPEC - - // SECP256k1 init - const epCurve = EllipticCurve.getSECCurveByName("secp256k1") - - - const ki = IL.add(BigInteger.fromByteArrayUnsigned(this.childPrivateKey)).mod(epCurve.getN()) // parse256(IL) + kpar (mod n) ==> ki - this.grandChildPrivateKey = ki.toByteArrayUnsigned() - - - // Call deriveExtendedPrivateChildKey - deriveExtendedPrivateGrandChildKey(2, 0) - } - - - const deriveExtendedPrivateGrandChildKey = (i, childIndex) => { - - // Serialization Variable - const s = [] - - // Append Version Byte - s.push(...(utils.int32ToBytes(this.versionBytes.mainnet.private))) - - // Append Depth (using the index as depth) - i = parseInt(i) - s.push(i) - - // Append Parent Fingerprint - s.push(...(this.childPublicKeyHash.slice(0, 4))) - - // Append Child Index - s.push(childIndex >>> 24) - s.push((childIndex >>> 16) & 0xff) - s.push((childIndex >>> 8) & 0xff) - s.push(childIndex & 0xff) - - // Append Chain Code - s.push(...this.grandChildChainCode) - - // Append 1 byte '0x00' (to make the key data 33 bytes, DO THIS ONLY FOR PRIVATE KEYS ) - s.push(0) - - // Append Private Key - s.push(...this.grandChildPrivateKey) - - // Generate CheckSum - const _s = new Uint8Array(s) - const _checkSum = new Sha256().process(new Sha256().process(_s).finish().result).finish().result - const checkSum = _checkSum.slice(0, 4) - - // Append CheckSum - s.push(...checkSum) // And this brings us to the end of the serialization... - - // Save to Private Key as Base58 encoded - this.xPrivateGrandChildKey = Base58.encode(s) - } - - const deriveExtendedPublicGrandChildKey = (i, childIndex) => { - - // Serialization Variable - const s = [] - - // Append Version Byte - s.push(...(utils.int32ToBytes(this.versionBytes.mainnet.public))) - - // Append Depth - i = parseInt(i) - s.push(i) - - // Append Parent Fingerprint - s.push(...(this.childPublicKeyHash.slice(0, 4))) - - // Append Child Index - s.push(childIndex >>> 24) - s.push((childIndex >>> 16) & 0xff) - s.push((childIndex >>> 8) & 0xff) - s.push(childIndex & 0xff) - - // Append Chain Code - s.push(...this.grandChildChainCode) - - // Append Public Key - s.push(...this.grandChildPublicKey) - - // Generate CheckSum - const _s = new Uint8Array(s) - const _checkSum = new Sha256().process(new Sha256().process(_s).finish().result).finish().result - const checkSum = _checkSum.slice(0, 4) - - // Append CheckSum - s.push(...checkSum) // And this brings us to the end of the serialization... - - // Save to Public Key as Base58 encoded - this.xPublicGrandChildKey = Base58.encode(s) - } - - - - // Hard Code value.. - let childIndex = 0 - - // Call derivePrivateChildKey //Hard code value - derivePrivateChildKey(childIndex) - - // Call derivePublicChildKey - derivePublicChildKey() - - - // Call derivePrivateGrandChildKey // Hard Code value... - derivePrivateGrandChildKey(0, 2) - - // Call derivePublicGrandChildKey - derivePublicGrandChildKey() - } - - returnWallet() { - - // Will be limiting the exported Wallet Object to just the Master keys and Legacy Addresses - - const wallet = { - derivedMasterPrivateKey: this.masterPrivateKey, - derivedMasterPublicKey: this.masterPublicKey, - _tDerivedMasterPrivateKey: this._tMasterPrivateKey, - _tDerivedmasterPublicKey: this._tmasterPublicKey, - seed58: this.seed58, - // derivedPrivateChildKey: this.xPrivateChildKey, - // derivedPublicChildKey: this.xPublicChildKey, - // derivedPrivateGrandChildKey: this.xPrivateGrandChildKey, - // derivedPublicGrandChildKey: this.xPublicGrandChildKey, - address: this.litecoinLegacyAddress, - _taddress: this._tlitecoinLegacyAddress - } - - this.wallet = wallet - return wallet - } -} diff --git a/src/encryption/AltcoinHDWallet.ts b/src/encryption/AltcoinHDWallet.ts new file mode 100644 index 0000000..0af6875 --- /dev/null +++ b/src/encryption/AltcoinHDWallet.ts @@ -0,0 +1,881 @@ +// @ts-nocheck +import Base58 from './Base58.js'; +import { Sha256, Sha512 } from 'asmcrypto.js'; +import jsSHA from 'jssha'; +import RIPEMD160 from './ripemd160.js'; +import utils from '../utils/utils.js'; +import { BigInteger, EllipticCurve } from './ecbn'; +import { Buffer } from 'buffer'; + +export default class AltcoinHDWallet { + constructor(addressParams) { + /** + * Seed - 32 bytes + */ + + this.seed = new Uint8Array(32); + + /** + * Version Bytes - 4 byte + */ + + this.versionBytes = addressParams; + + /** + * Depth - 1 byte + */ + + this.depth = 0; + + /** + * Parent Fingerprint - 4 bytes + */ + + this.parentFingerprint = '0x00000000'; // master key + + /** + * Child Index - 4 bytes + */ + + this.childIndex = '0x00000000'; // master key + + /** + * Chain Code - 32 bytes + */ + + this.chainCode = new Uint8Array(32); + + /** + * Key Data - 33 bytes + */ + + this.keyData = new Uint8Array(33); + + /** + * Seed Hash - 64 bytes + */ + + this.seedHash = new Uint8Array(64); + + /** + * Private Key - 32 bytes + */ + + this.privateKey = new Uint8Array(32); + + /** + * Public Key - 33 bytes (compressed) + */ + + this.publicKey = new Uint8Array(33); + + /** + * Public Key Hash160 (used to derive the parent fingerprint for derived) + */ + + this.publicKeyHash = new Uint8Array(20); + + /** + * Master Private Key (Base58 encoded) + */ + + this.masterPrivateKey = ''; + + /** + * Master Public Key (Base58 encoded) + */ + + this.masterPublicKey = ''; + + /** + * Testnet Master Private Key (Base58 encoded) - THIS IS TESTNET + */ + + this._tMasterPrivateKey = ''; + + /** + * Testnet Master Public Key (Base58 encoded) - THIS IS TESTNET + */ + + this._tmasterPublicKey = ''; + + /** + * Child Keys Derivation from the Parent Keys + */ + + /** + * Child Private Key - 32 bytes + */ + + this.childPrivateKey = new Uint8Array(32); + + /** + * Child Chain Code - 32 bytes + */ + + this.childChainCode = new Uint8Array(32); + + /** + * Child Public Key - 33 bytes (compressed) + */ + + this.childPublicKey = new Uint8Array(33); + + /** + * Child Public Key Hash160 (used to derive the parent fingerprint for derived) + */ + + this.childPublicKeyHash = new Uint8Array(20); + + /** + * Extended Private Child Key - Base58 encoded + */ + + this.xPrivateChildKey = ''; + + /** + * Extended Public Child Key - Base58 encoded + */ + + this.xPublicChildKey = ''; + + /** + * Grand Child Keys Derivation from the Child Keys + */ + + /** + * Grand Child Private Key - 32 bytes + */ + + this.grandChildPrivateKey = new Uint8Array(32); + + /** + * Grand Child Chain Code - 32 bytes + */ + + this.grandChildChainCode = new Uint8Array(32); + + /** + * Grand Child Public Key - 33 bytes (compressed) + */ + + this.grandChildPublicKey = new Uint8Array(33); + + /** + * Grand Public Key Hash160 (used to derive the parent fingerprint for derived) + */ + + this.grandChildPublicKeyHash = new Uint8Array(20); + + /** + * Extended Private Grand Child Key - Base58 encoded + */ + + this.xPrivateGrandChildKey = ''; + + /** + * Extended Public Grand Child Key - Base58 encoded + */ + + this.xPublicGrandChildKey = ''; + + /** + * Litecoin Legacy Address - Derived from the Grand Child Public Key Hash + */ + + this.litecoinLegacyAddress = ''; + + /** + * TESTNET Litecoin Legacy Address (Derived from the Grand Child Public Key Hash) - THIS IS TESTNET + */ + + this._tlitecoinLegacyAddress = ''; + + /** + * Wallet - Wallet Object (keys...) + */ + + this.wallet = {}; + } + + setSeed(seed) { + this.seed = seed; + } + + createWallet(seed, isBIP44, indicator = null) { + // Set Seeed + this.setSeed(seed); + + // Generate Seed Hash + this.generateSeedHash(this.seed, isBIP44, indicator); + + // Generate Private Key + this.generatePrivateKey(this.seedHash); + + // Generate Chain Code + this.generateChainCode(this.seedHash); + + // Generate Public Key from Private Key + this.generatePublicKey(this.privateKey); + + // Generate Mainnet Master Private Key + this.generateMainnetMasterPrivateKey(); + + // Generate Mainnet Master Public Key + this.generateMainnetMasterPublicKey(); + + // Generate Testnet Master Private Key + this.generateTestnetMasterPrivateKey(); + + // Generate Testnet Master Public Key + this.generateTestnetMasterPublicKey(); + + // Generate Child and Grand Child Keys + this.generateDerivedChildKeys(); + + // Return Wallet Object Specification + return this.returnWallet(); + } + + generateSeedHash(seed, isBIP44, indicator = null) { + let buffer; + + if (isBIP44) { + buffer = utils.appendBuffer( + seed.reverse(), + utils.int32ToBytes(indicator) + ); + } else { + if (indicator !== null) { + const indicatorString = utils.stringtoUTF8Array(indicator); + buffer = utils.appendBuffer(seed.reverse(), indicatorString); + } else { + buffer = seed.reverse(); + } + } + + const _reverseSeedHash = new Sha256().process(buffer).finish().result; + this.seedHash = new Sha512() + .process(utils.appendBuffer(seed, _reverseSeedHash)) + .finish().result; + } + + generatePrivateKey(seedHash) { + const SECP256K1_CURVE_ORDER = new BigInteger( + '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141' + ); + + const privateKeyHash = seedHash.slice(0, 32); + + this.seed58 = Base58.encode(privateKeyHash); + + const _privateKeyHash = [...privateKeyHash]; + let privateKeyBigInt = BigInteger.fromByteArrayUnsigned(_privateKeyHash); + + const privateKey = privateKeyBigInt + .mod(SECP256K1_CURVE_ORDER.subtract(BigInteger.ONE)) + .add(BigInteger.ONE); + this.privateKey = privateKey.toByteArrayUnsigned(); + } + + generateChainCode(seedHash) { + this.chainCode = new Sha256() + .process(seedHash.slice(32, 64)) + .finish().result; + } + + generatePublicKey(privateKey) { + const _privateKey = [...privateKey]; + const privateKeyBigInt = BigInteger.fromByteArrayUnsigned(_privateKey); + + const epCurve = EllipticCurve.getSECCurveByName('secp256k1'); + const curvePoints = epCurve.getG().multiply(privateKeyBigInt); + + const x = curvePoints.getX().toBigInteger(); + const y = curvePoints.getY().toBigInteger(); + + /** + * Deriving Uncompressed Public Key (65 bytes) + * + * const publicKeyBytes = EllipticCurve.integerToBytes(x, 32) + * this.publicKey = publicKeyBytes.concat(EllipticCurve.integerToBytes(y, 32)) + * this.publicKey.unshift(0x04) // append point indicator + */ + + // Compressed Public Key (33 bytes) + this.publicKey = EllipticCurve.integerToBytes(x, 32); + + if (y.isEven()) { + this.publicKey.unshift(0x02); // append point indicator + } else { + this.publicKey.unshift(0x03); // append point indicator + } + + // PublicKey Hash + const publicKeySHA256 = new Sha256() + .process(new Uint8Array(this.publicKey)) + .finish().result; + const _publicKeyHash = new RIPEMD160() + .update(Buffer.from(publicKeySHA256)) + .digest('hex'); + this.publicKeyHash = _publicKeyHash; + } + + generateMainnetMasterPrivateKey() { + // Serialization Variable + const s = []; + + // Append Version Byte + s.push(...utils.int32ToBytes(this.versionBytes.mainnet.private)); + + // Append Depth + s.push(this.depth); + + // Append Parent Fingerprint + s.push(...utils.int32ToBytes(this.parentFingerprint)); + + // Append Child Number + s.push(...utils.int32ToBytes(this.childIndex)); + + // Append Chain Code + s.push(...this.chainCode); + + // Append 1 byte '0x00' (to make the key data 33 bytes, DO THIS ONLY FOR PRIVATE KEYS ) + s.push(0); + + //if the private key length is less than 32 let's add leading zeros + if (this.privateKey.length < 32) { + for (let i = this.privateKey.length; i < 32; i++) { + s.push(0); + } + } + + // Append Private Key + s.push(...this.privateKey); + + // Generate CheckSum + const _s = new Uint8Array(s); + const _checkSum = new Sha256() + .process(new Sha256().process(_s).finish().result) + .finish().result; + const checkSum = _checkSum.slice(0, 4); + + // Append CheckSum + s.push(...checkSum); // And this brings us to the end of the serialization... + + // Save to Private Key as Base58 encoded + this.masterPrivateKey = Base58.encode(s); + } + + generateMainnetMasterPublicKey() { + // Serialization Variable + const s = []; + + // Append Version Byte + s.push(...utils.int32ToBytes(this.versionBytes.mainnet.public)); + + // Append Depth + s.push(this.depth); + + // Append Parent Fingerprint + s.push(...utils.int32ToBytes(this.parentFingerprint)); + + // Append Child Number + s.push(...utils.int32ToBytes(this.childIndex)); + + // Append Chain Code + s.push(...this.chainCode); + + // Append Public Key + s.push(...this.publicKey); + + // Generate CheckSum + const _s = new Uint8Array(s); + const _checkSum = new Sha256() + .process(new Sha256().process(_s).finish().result) + .finish().result; + const checkSum = _checkSum.slice(0, 4); + + // Append CheckSum + s.push(...checkSum); // And this brings us to the end of the serialization... + + // Save to Public Key as Base58 encoded + this.masterPublicKey = Base58.encode(s); + } + + generateTestnetMasterPrivateKey() { + // To be Used ONLY in Testnet... + + // Serialization Variable + const s = []; + + // Append Version Byte + s.push(...utils.int32ToBytes(this.versionBytes.testnet.private)); + + // Append Depth + s.push(this.depth); + + // Append Parent Fingerprint + s.push(...utils.int32ToBytes(this.parentFingerprint)); + + // Append Child Number + s.push(...utils.int32ToBytes(this.childIndex)); + + // Append Chain Code + s.push(...this.chainCode); + + // Append 1 byte '0x00' (to make the key data 33 bytes, DO THIS ONLY FOR PRIVATE KEYS ) + s.push(0); + + // Append Private Key + s.push(...this.privateKey); + + // Generate CheckSum + const _s = new Uint8Array(s); + const _checkSum = new Sha256() + .process(new Sha256().process(_s).finish().result) + .finish().result; + const checkSum = _checkSum.slice(0, 4); + + // Append CheckSum + s.push(...checkSum); // And this brings us to the end of the serialization... + + // Save to Private Key as Base58 encoded + this._tMasterPrivateKey = Base58.encode(s); + } + + generateTestnetMasterPublicKey() { + // To be Used ONLY in Testnet... + + // Serialization Variable + const s = []; + + // Append Version Byte + s.push(...utils.int32ToBytes(this.versionBytes.testnet.public)); + + // Append Depth + s.push(this.depth); + + // Append Parent Fingerprint + s.push(...utils.int32ToBytes(this.parentFingerprint)); + + // Append Child Number + s.push(...utils.int32ToBytes(this.childIndex)); + + // Append Chain Code + s.push(...this.chainCode); + + // Append Private Key + s.push(...this.publicKey); + + // Generate CheckSum + const _s = new Uint8Array(s); + const _checkSum = new Sha256() + .process(new Sha256().process(_s).finish().result) + .finish().result; + const checkSum = _checkSum.slice(0, 4); + + // Append CheckSum + s.push(...checkSum); // And this brings us to the end of the serialization... + + // Save to Private Key as Base58 encoded + this._tmasterPublicKey = Base58.encode(s); + } + + generateDerivedChildKeys() { + // SPEC INFO: https://en.bitcoin.it/wiki/BIP_0032#Child_key_derivation_.28CKD.29_functions + // NOTE: will not be using some of derivations func as the value is known. (So I'd rather shove in the values and rewrite out the derivations later ?) + + // NOTE: I "re-wrote" and "reduplicate" the code for child and grandChild keys derivations inorder to get the child and grandchild from the child + // TODO: Make this more better in the future + + const path = 'm/0/0'; + // let p = path.split('/') + + // Get Public kEY + const derivePublicChildKey = () => { + const _privateKey = [...this.childPrivateKey]; + const privateKeyBigInt = BigInteger.fromByteArrayUnsigned(_privateKey); + + const epCurve = EllipticCurve.getSECCurveByName('secp256k1'); + const curvePoints = epCurve.getG().multiply(privateKeyBigInt); + + const x = curvePoints.getX().toBigInteger(); + const y = curvePoints.getY().toBigInteger(); + + // Compressed Public Key (33 bytes) + this.childPublicKey = EllipticCurve.integerToBytes(x, 32); + + if (y.isEven()) { + this.childPublicKey.unshift(0x02); // append point indicator + } else { + this.childPublicKey.unshift(0x03); // append point indicator + } + + // PublicKey Hash + const childPublicKeySHA256 = new Sha256() + .process(new Uint8Array(this.childPublicKey)) + .finish().result; + const _childPublicKeyHash = new RIPEMD160() + .update(Buffer.from(childPublicKeySHA256)) + .digest('hex'); + this.childPublicKeyHash = _childPublicKeyHash; + + // Call deriveExtendedPublicChildKey // WIll be hardcoding the values... + deriveExtendedPublicChildKey(1, 0); + }; + + const derivePrivateChildKey = (cI) => { + let ib = []; + ib.push((cI >> 24) & 0xff); + ib.push((cI >> 16) & 0xff); + ib.push((cI >> 8) & 0xff); + ib.push(cI & 0xff); + + const s = [...this.publicKey].concat(ib); + + const _hmacSha512 = new jsSHA('SHA-512', 'UINT8ARRAY', { + numRounds: 1, + hmacKey: { value: this.chainCode, format: 'UINT8ARRAY' }, + }); + _hmacSha512.update(new Uint8Array(s)); + + const IL = BigInteger.fromByteArrayUnsigned([ + ..._hmacSha512.getHMAC('UINT8ARRAY').slice(0, 32), + ]); + this.childChainCode = _hmacSha512.getHMAC('UINT8ARRAY').slice(32, 64); // IR according to the SPEC + + // SECP256k1 init + const epCurve = EllipticCurve.getSECCurveByName('secp256k1'); + + const ki = IL.add(BigInteger.fromByteArrayUnsigned(this.privateKey)).mod( + epCurve.getN() + ); // parse256(IL) + kpar (mod n) ==> ki + this.childPrivateKey = ki.toByteArrayUnsigned(); + + // Call deriveExtendedPrivateChildKey + deriveExtendedPrivateChildKey(1, 0); + }; + + const deriveExtendedPrivateChildKey = (i, childIndex) => { + // Serialization Variable + const s = []; + + // Append Version Byte + s.push(...utils.int32ToBytes(this.versionBytes.mainnet.private)); + + // Append Depth (using the index as depth) + i = parseInt(i); + s.push(i); + + // Append Parent Fingerprint + s.push(...this.publicKeyHash.slice(0, 4)); + + // Append Child Index + s.push(childIndex >>> 24); + s.push((childIndex >>> 16) & 0xff); + s.push((childIndex >>> 8) & 0xff); + s.push(childIndex & 0xff); + + // Append Chain Code + s.push(...this.childChainCode); + + // Append 1 byte '0x00' (to make the key data 33 bytes, DO THIS ONLY FOR PRIVATE KEYS ) + s.push(0); + + // Append Private Key + s.push(...this.childPrivateKey); + + // Generate CheckSum + const _s = new Uint8Array(s); + const _checkSum = new Sha256() + .process(new Sha256().process(_s).finish().result) + .finish().result; + const checkSum = _checkSum.slice(0, 4); + + // Append CheckSum + s.push(...checkSum); // And this brings us to the end of the serialization... + + // Save to Private Key as Base58 encoded + this.xPrivateChildKey = Base58.encode(s); + }; + + const deriveExtendedPublicChildKey = (i, childIndex) => { + // Serialization Variable + const s = []; + + // Append Version Byte + s.push(...utils.int32ToBytes(this.versionBytes.mainnet.public)); + + // Append Depth + i = parseInt(i); + s.push(i); + + // Append Parent Fingerprint + s.push(...this.publicKeyHash.slice(0, 4)); + + // Append Child Index + s.push(childIndex >>> 24); + s.push((childIndex >>> 16) & 0xff); + s.push((childIndex >>> 8) & 0xff); + s.push(childIndex & 0xff); + + // Append Chain Code + s.push(...this.childChainCode); + + // Append Public Key + s.push(...this.childPublicKey); + + // Generate CheckSum + const _s = new Uint8Array(s); + const _checkSum = new Sha256() + .process(new Sha256().process(_s).finish().result) + .finish().result; + const checkSum = _checkSum.slice(0, 4); + + // Append CheckSum + s.push(...checkSum); // And this brings us to the end of the serialization... + + // Save to Public Key as Base58 encoded + this.xPublicChildKey = Base58.encode(s); + }; + + /** + * GRAND CHILD KEYS + * + * NOTE: I know this is not the best way to generate this (even though it works the way it ought) + * Things to rewrite will be and not limited to deriving this through a for loop, removing hard code values, etc... + */ + + const derivePublicGrandChildKey = () => { + const _privateKey = [...this.grandChildPrivateKey]; + const privateKeyBigInt = BigInteger.fromByteArrayUnsigned(_privateKey); + + const epCurve = EllipticCurve.getSECCurveByName('secp256k1'); + const curvePoints = epCurve.getG().multiply(privateKeyBigInt); + + const x = curvePoints.getX().toBigInteger(); + const y = curvePoints.getY().toBigInteger(); + + // Compressed Public Key (33 bytes) + this.grandChildPublicKey = EllipticCurve.integerToBytes(x, 32); + + if (y.isEven()) { + this.grandChildPublicKey.unshift(0x02); // append point indicator + } else { + this.grandChildPublicKey.unshift(0x03); // append point indicator + } + + // PublicKey Hash + const grandChildPublicKeySHA256 = new Sha256() + .process(new Uint8Array(this.grandChildPublicKey)) + .finish().result; + const _grandChildPublicKeyHash = new RIPEMD160() + .update(Buffer.from(grandChildPublicKeySHA256)) + .digest('hex'); + this.grandChildPublicKeyHash = _grandChildPublicKeyHash; + + // Call deriveExtendedPublicChildKey // WIll be hardcoding the values... + deriveExtendedPublicGrandChildKey(2, 0); + + /** + * Derive Litecoin Legacy Address + */ + + // Append Address Prefix + let prefix = [this.versionBytes.mainnet.prefix]; + if (2 == this.versionBytes.mainnet.prefix.length) { + prefix = [this.versionBytes.mainnet.prefix[0]]; + prefix.push([this.versionBytes.mainnet.prefix[1]]); + } + + const k = prefix.concat(...this.grandChildPublicKeyHash); + + // Derive Checksum + const _addressCheckSum = new Sha256() + .process(new Sha256().process(new Uint8Array(k)).finish().result) + .finish().result; + const addressCheckSum = _addressCheckSum.slice(0, 4); + + // Append CheckSum + const _litecoinLegacyAddress = k.concat(...addressCheckSum); + + // Convert to Base58 + this.litecoinLegacyAddress = Base58.encode(_litecoinLegacyAddress); + + /** + * Derive TESTNET Litecoin Legacy Address + */ + + // Append Version Byte + const tK = [this.versionBytes.testnet.prefix].concat( + ...this.grandChildPublicKeyHash + ); + + // Derive Checksum + const _tAddressCheckSum = new Sha256() + .process(new Sha256().process(new Uint8Array(tK)).finish().result) + .finish().result; + const tAddressCheckSum = _tAddressCheckSum.slice(0, 4); + + // Append CheckSum + const _tlitecoinLegacyAddress = tK.concat(...tAddressCheckSum); + + // Convert to Base58 + this._tlitecoinLegacyAddress = Base58.encode(_tlitecoinLegacyAddress); + }; + + const derivePrivateGrandChildKey = (cI, i) => { + let ib = []; + ib.push((cI >> 24) & 0xff); + ib.push((cI >> 16) & 0xff); + ib.push((cI >> 8) & 0xff); + ib.push(cI & 0xff); + + const s = [...this.childPublicKey].concat(ib); + + const _hmacSha512 = new jsSHA('SHA-512', 'UINT8ARRAY', { + numRounds: 1, + hmacKey: { value: this.childChainCode, format: 'UINT8ARRAY' }, + }); + _hmacSha512.update(new Uint8Array(s)); + + const IL = BigInteger.fromByteArrayUnsigned([ + ..._hmacSha512.getHMAC('UINT8ARRAY').slice(0, 32), + ]); + this.grandChildChainCode = _hmacSha512 + .getHMAC('UINT8ARRAY') + .slice(32, 64); // IR according to the SPEC + + // SECP256k1 init + const epCurve = EllipticCurve.getSECCurveByName('secp256k1'); + + const ki = IL.add( + BigInteger.fromByteArrayUnsigned(this.childPrivateKey) + ).mod(epCurve.getN()); // parse256(IL) + kpar (mod n) ==> ki + this.grandChildPrivateKey = ki.toByteArrayUnsigned(); + + // Call deriveExtendedPrivateChildKey + deriveExtendedPrivateGrandChildKey(2, 0); + }; + + const deriveExtendedPrivateGrandChildKey = (i, childIndex) => { + // Serialization Variable + const s = []; + + // Append Version Byte + s.push(...utils.int32ToBytes(this.versionBytes.mainnet.private)); + + // Append Depth (using the index as depth) + i = parseInt(i); + s.push(i); + + // Append Parent Fingerprint + s.push(...this.childPublicKeyHash.slice(0, 4)); + + // Append Child Index + s.push(childIndex >>> 24); + s.push((childIndex >>> 16) & 0xff); + s.push((childIndex >>> 8) & 0xff); + s.push(childIndex & 0xff); + + // Append Chain Code + s.push(...this.grandChildChainCode); + + // Append 1 byte '0x00' (to make the key data 33 bytes, DO THIS ONLY FOR PRIVATE KEYS ) + s.push(0); + + // Append Private Key + s.push(...this.grandChildPrivateKey); + + // Generate CheckSum + const _s = new Uint8Array(s); + const _checkSum = new Sha256() + .process(new Sha256().process(_s).finish().result) + .finish().result; + const checkSum = _checkSum.slice(0, 4); + + // Append CheckSum + s.push(...checkSum); // And this brings us to the end of the serialization... + + // Save to Private Key as Base58 encoded + this.xPrivateGrandChildKey = Base58.encode(s); + }; + + const deriveExtendedPublicGrandChildKey = (i, childIndex) => { + // Serialization Variable + const s = []; + + // Append Version Byte + s.push(...utils.int32ToBytes(this.versionBytes.mainnet.public)); + + // Append Depth + i = parseInt(i); + s.push(i); + + // Append Parent Fingerprint + s.push(...this.childPublicKeyHash.slice(0, 4)); + + // Append Child Index + s.push(childIndex >>> 24); + s.push((childIndex >>> 16) & 0xff); + s.push((childIndex >>> 8) & 0xff); + s.push(childIndex & 0xff); + + // Append Chain Code + s.push(...this.grandChildChainCode); + + // Append Public Key + s.push(...this.grandChildPublicKey); + + // Generate CheckSum + const _s = new Uint8Array(s); + const _checkSum = new Sha256() + .process(new Sha256().process(_s).finish().result) + .finish().result; + const checkSum = _checkSum.slice(0, 4); + + // Append CheckSum + s.push(...checkSum); // And this brings us to the end of the serialization... + + // Save to Public Key as Base58 encoded + this.xPublicGrandChildKey = Base58.encode(s); + }; + + // Hard Code value.. + let childIndex = 0; + + // Call derivePrivateChildKey //Hard code value + derivePrivateChildKey(childIndex); + + // Call derivePublicChildKey + derivePublicChildKey(); + + // Call derivePrivateGrandChildKey // Hard Code value... + derivePrivateGrandChildKey(0, 2); + + // Call derivePublicGrandChildKey + derivePublicGrandChildKey(); + } + + returnWallet() { + // Will be limiting the exported Wallet Object to just the Master keys and Legacy Addresses + + const wallet = { + derivedMasterPrivateKey: this.masterPrivateKey, + derivedMasterPublicKey: this.masterPublicKey, + _tDerivedMasterPrivateKey: this._tMasterPrivateKey, + _tDerivedmasterPublicKey: this._tmasterPublicKey, + seed58: this.seed58, + // derivedPrivateChildKey: this.xPrivateChildKey, + // derivedPublicChildKey: this.xPublicChildKey, + // derivedPrivateGrandChildKey: this.xPrivateGrandChildKey, + // derivedPublicGrandChildKey: this.xPublicGrandChildKey, + address: this.litecoinLegacyAddress, + _taddress: this._tlitecoinLegacyAddress, + }; + + this.wallet = wallet; + return wallet; + } +} diff --git a/src/deps/Base58.ts b/src/encryption/Base58.ts similarity index 100% rename from src/deps/Base58.ts rename to src/encryption/Base58.ts diff --git a/src/deps/bcryptworker.worker.js b/src/encryption/bcryptworker.worker.js similarity index 100% rename from src/deps/bcryptworker.worker.js rename to src/encryption/bcryptworker.worker.js diff --git a/src/deps/bcryptworkerwasm.worker.js b/src/encryption/bcryptworkerwasm.worker.js similarity index 100% rename from src/deps/bcryptworkerwasm.worker.js rename to src/encryption/bcryptworkerwasm.worker.js diff --git a/src/deps/broken-ripemd160.ts b/src/encryption/broken-ripemd160.ts similarity index 100% rename from src/deps/broken-ripemd160.ts rename to src/encryption/broken-ripemd160.ts diff --git a/src/deps/ecbn.ts b/src/encryption/ecbn.ts similarity index 100% rename from src/deps/ecbn.ts rename to src/encryption/ecbn.ts diff --git a/src/deps/ed2curve.ts b/src/encryption/ed2curve.ts similarity index 100% rename from src/deps/ed2curve.ts rename to src/encryption/ed2curve.ts diff --git a/src/backgroundFunctions/encryption.ts b/src/encryption/encryption.ts similarity index 96% rename from src/backgroundFunctions/encryption.ts rename to src/encryption/encryption.ts index aa2c023..2016166 100644 --- a/src/backgroundFunctions/encryption.ts +++ b/src/encryption/encryption.ts @@ -1,14 +1,14 @@ -import { getBaseApi } from '../background'; -import i18n from '../i18n/i18n'; +import { getBaseApi } from '../background/background.ts'; +import i18n from '../i18n/i18n.ts'; import { createSymmetricKeyAndNonce, decryptGroupData, encryptDataGroup, objectToBase64, -} from '../qdn/encryption/group-encryption'; -import { publishData } from '../qdn/publish/pubish'; -import { getData } from '../utils/chromeStorage'; -import { RequestQueueWithPromise } from '../utils/queue/queue'; +} from '../qdn/encryption/group-encryption.ts'; +import { publishData } from '../qdn/publish/pubish.ts'; +import { getData } from '../utils/chromeStorage.ts'; +import { RequestQueueWithPromise } from '../utils/queue/queue.ts'; export const requestQueueGetPublicKeys = new RequestQueueWithPromise(10); diff --git a/src/deps/kdf.ts b/src/encryption/kdf.ts similarity index 100% rename from src/deps/kdf.ts rename to src/encryption/kdf.ts diff --git a/src/deps/nacl-fast.ts b/src/encryption/nacl-fast.ts similarity index 100% rename from src/deps/nacl-fast.ts rename to src/encryption/nacl-fast.ts diff --git a/src/deps/ripemd160.ts b/src/encryption/ripemd160.ts similarity index 100% rename from src/deps/ripemd160.ts rename to src/encryption/ripemd160.ts diff --git a/src/hooks/useHandlePaymentNotification.tsx b/src/hooks/useHandlePaymentNotification.tsx index 7a13072..e25fae2 100644 --- a/src/hooks/useHandlePaymentNotification.tsx +++ b/src/hooks/useHandlePaymentNotification.tsx @@ -1,7 +1,10 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { getBaseApiReact } from '../App'; import { getData, storeData } from '../utils/chromeStorage'; -import { checkDifference, getNameInfoForOthers } from '../background'; +import { + checkDifference, + getNameInfoForOthers, +} from '../background/background.ts'; import { lastPaymentSeenTimestampAtom } from '../atoms/global'; import { subscribeToEvent, unsubscribeFromEvent } from '../utils/events'; import { useAtom } from 'jotai'; diff --git a/src/hooks/useHandlePrivateApps.tsx b/src/hooks/useHandlePrivateApps.tsx index e1d8288..3876130 100644 --- a/src/hooks/useHandlePrivateApps.tsx +++ b/src/hooks/useHandlePrivateApps.tsx @@ -1,14 +1,14 @@ import { useContext, useState } from 'react'; import { executeEvent } from '../utils/events'; import { getBaseApiReact, QORTAL_APP_CONTEXT } from '../App'; -import { createEndpoint } from '../background'; +import { createEndpoint } from '../background/background.ts'; import { settingsLocalLastUpdatedAtom, sortablePinnedAppsAtom, } from '../atoms/global'; import { saveToLocalStorage } from '../components/Apps/AppsNavBarDesktop'; import { base64ToUint8Array } from '../qdn/encryption/group-encryption'; -import { uint8ArrayToObject } from '../backgroundFunctions/encryption'; +import { uint8ArrayToObject } from '../encryption/encryption.ts'; import { useSetAtom } from 'jotai'; import { useTranslation } from 'react-i18next'; diff --git a/src/hooks/useQortalGetSaveSettings.tsx b/src/hooks/useQortalGetSaveSettings.tsx index 214455b..5fb62fc 100644 --- a/src/hooks/useQortalGetSaveSettings.tsx +++ b/src/hooks/useQortalGetSaveSettings.tsx @@ -12,7 +12,7 @@ import { decryptResource } from '../components/Group/Group'; import { base64ToUint8Array, uint8ArrayToObject, -} from '../backgroundFunctions/encryption'; +} from '../encryption/encryption'; import { useAtom, useSetAtom } from 'jotai'; function fetchFromLocalStorage(key) { diff --git a/src/qdn/encryption/group-encryption.ts b/src/qdn/encryption/group-encryption.ts index 24998e9..6f018ba 100644 --- a/src/qdn/encryption/group-encryption.ts +++ b/src/qdn/encryption/group-encryption.ts @@ -1,8 +1,8 @@ // @ts-nocheck -import Base58 from '../../deps/Base58'; -import ed2curve from '../../deps/ed2curve'; -import nacl from '../../deps/nacl-fast'; +import Base58 from '../../encryption/Base58'; +import ed2curve from '../../encryption/ed2curve'; +import nacl from '../../encryption/nacl-fast'; import i18n from '../../i18n/i18n'; export function base64ToUint8Array(base64: string) { diff --git a/src/qdn/publish/pubish.ts b/src/qdn/publish/pubish.ts index 08c4d15..201fc0c 100644 --- a/src/qdn/publish/pubish.ts +++ b/src/qdn/publish/pubish.ts @@ -1,266 +1,289 @@ // @ts-nocheck -import { Buffer } from "buffer" -import Base58 from "../../deps/Base58" -import nacl from "../../deps/nacl-fast" -import utils from "../../utils/utils" -import { createEndpoint, getBaseApi } from "../../background"; -import { getData } from "../../utils/chromeStorage"; +import { Buffer } from 'buffer'; +import Base58 from '../../encryption/Base58'; +import nacl from '../../encryption/nacl-fast'; +import utils from '../../utils/utils'; +import { createEndpoint, getBaseApi } from '../../background/background'; +import { getData } from '../../utils/chromeStorage'; -export async function reusableGet(endpoint){ - const validApi = await getBaseApi(); - - const response = await fetch(validApi + endpoint); - const data = await response.json(); - return data - } - - async function reusablePost(endpoint, _body){ - // const validApi = await findUsableApi(); - const url = await createEndpoint(endpoint) - const response = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: _body +export async function reusableGet(endpoint) { + const validApi = await getBaseApi(); + + const response = await fetch(validApi + endpoint); + const data = await response.json(); + return data; +} + +async function reusablePost(endpoint, _body) { + // const validApi = await findUsableApi(); + const url = await createEndpoint(endpoint); + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: _body, }); - let data + let data; try { - data = await response.clone().json() + data = await response.clone().json(); } catch (e) { - data = await response.text() - } - return data + data = await response.text(); } + return data; +} async function getKeyPair() { - const res = await getData("keyPair").catch(() => null); - if (res) { - return res - } else { - throw new Error("Wallet not authenticated"); - } + const res = await getData('keyPair').catch(() => null); + if (res) { + return res; + } else { + throw new Error('Wallet not authenticated'); } +} export const publishData = async ({ - registeredName, - file, - service, - identifier, - uploadType, - isBase64, - filename, - withFee, - title, - description, - category, - tag1, - tag2, - tag3, - tag4, - tag5, - feeAmount + registeredName, + file, + service, + identifier, + uploadType, + isBase64, + filename, + withFee, + title, + description, + category, + tag1, + tag2, + tag3, + tag4, + tag5, + feeAmount, }: any) => { - - const validateName = async (receiverName: string) => { - return await reusableGet(`/names/${receiverName}`) - } + const validateName = async (receiverName: string) => { + return await reusableGet(`/names/${receiverName}`); + }; - const convertBytesForSigning = async (transactionBytesBase58: string) => { - return await reusablePost('/transactions/convert', transactionBytesBase58) - } + const convertBytesForSigning = async (transactionBytesBase58: string) => { + return await reusablePost('/transactions/convert', transactionBytesBase58); + }; - const getArbitraryFee = async () => { - const timestamp = Date.now() + const getArbitraryFee = async () => { + const timestamp = Date.now(); - let fee = await reusableGet(`/transactions/unitfee?txType=ARBITRARY×tamp=${timestamp}`) + let fee = await reusableGet( + `/transactions/unitfee?txType=ARBITRARY×tamp=${timestamp}` + ); - return { - timestamp, - fee: Number(fee), - feeToShow: (Number(fee) / 1e8).toFixed(8) - } - } + return { + timestamp, + fee: Number(fee), + feeToShow: (Number(fee) / 1e8).toFixed(8), + }; + }; - const signArbitraryWithFee = (arbitraryBytesBase58, arbitraryBytesForSigningBase58, keyPair) => { - if (!arbitraryBytesBase58) { - throw new Error('ArbitraryBytesBase58 not defined') - } - - if (!keyPair) { - throw new Error('keyPair not defined') - } - - const arbitraryBytes = Base58.decode(arbitraryBytesBase58) - const _arbitraryBytesBuffer = Object.keys(arbitraryBytes).map(function (key) { return arbitraryBytes[key]; }) - const arbitraryBytesBuffer = new Uint8Array(_arbitraryBytesBuffer) - const arbitraryBytesForSigning = Base58.decode(arbitraryBytesForSigningBase58) - const _arbitraryBytesForSigningBuffer = Object.keys(arbitraryBytesForSigning).map(function (key) { return arbitraryBytesForSigning[key]; }) - const arbitraryBytesForSigningBuffer = new Uint8Array(_arbitraryBytesForSigningBuffer) - const signature = nacl.sign.detached(arbitraryBytesForSigningBuffer, keyPair.privateKey) - - return utils.appendBuffer(arbitraryBytesBuffer, signature) + const signArbitraryWithFee = ( + arbitraryBytesBase58, + arbitraryBytesForSigningBase58, + keyPair + ) => { + if (!arbitraryBytesBase58) { + throw new Error('ArbitraryBytesBase58 not defined'); } - const processTransactionVersion2 = async (bytes) => { + if (!keyPair) { + throw new Error('keyPair not defined'); + } - return await reusablePost('/transactions/process?apiVersion=2', Base58.encode(bytes)) - } + const arbitraryBytes = Base58.decode(arbitraryBytesBase58); + const _arbitraryBytesBuffer = Object.keys(arbitraryBytes).map( + function (key) { + return arbitraryBytes[key]; + } + ); + const arbitraryBytesBuffer = new Uint8Array(_arbitraryBytesBuffer); + const arbitraryBytesForSigning = Base58.decode( + arbitraryBytesForSigningBase58 + ); + const _arbitraryBytesForSigningBuffer = Object.keys( + arbitraryBytesForSigning + ).map(function (key) { + return arbitraryBytesForSigning[key]; + }); + const arbitraryBytesForSigningBuffer = new Uint8Array( + _arbitraryBytesForSigningBuffer + ); + const signature = nacl.sign.detached( + arbitraryBytesForSigningBuffer, + keyPair.privateKey + ); - const signAndProcessWithFee = async (transactionBytesBase58: string) => { - let convertedBytesBase58 = await convertBytesForSigning( - transactionBytesBase58 - ) + return utils.appendBuffer(arbitraryBytesBuffer, signature); + }; - - if (convertedBytesBase58.error) { - throw new Error('Error when signing') - } + const processTransactionVersion2 = async (bytes) => { + return await reusablePost( + '/transactions/process?apiVersion=2', + Base58.encode(bytes) + ); + }; + const signAndProcessWithFee = async (transactionBytesBase58: string) => { + let convertedBytesBase58 = await convertBytesForSigning( + transactionBytesBase58 + ); - const resKeyPair = await getKeyPair() - const parsedData = resKeyPair - const uint8PrivateKey = Base58.decode(parsedData.privateKey); - const uint8PublicKey = Base58.decode(parsedData.publicKey); - const keyPair = { - privateKey: uint8PrivateKey, - publicKey: uint8PublicKey - }; + if (convertedBytesBase58.error) { + throw new Error('Error when signing'); + } - let signedArbitraryBytes = signArbitraryWithFee(transactionBytesBase58, convertedBytesBase58, keyPair) - const response = await processTransactionVersion2(signedArbitraryBytes) + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey, + }; - let myResponse = { error: '' } + let signedArbitraryBytes = signArbitraryWithFee( + transactionBytesBase58, + convertedBytesBase58, + keyPair + ); + const response = await processTransactionVersion2(signedArbitraryBytes); - if (response === false) { - throw new Error('Error when signing') - } else { - myResponse = response - } + let myResponse = { error: '' }; - return myResponse - } + if (response === false) { + throw new Error('Error when signing'); + } else { + myResponse = response; + } - const validate = async () => { - let validNameRes = await validateName(registeredName) + return myResponse; + }; - if (validNameRes.error) { - throw new Error('Name not found') - } + const validate = async () => { + let validNameRes = await validateName(registeredName); - let fee = null + if (validNameRes.error) { + throw new Error('Name not found'); + } - if (withFee && feeAmount) { - fee = feeAmount - } else if (withFee) { - const res = await getArbitraryFee() - if (res.fee) { - fee = res.fee - } else { - throw new Error('unable to get fee') - } - } - - let transactionBytes = await uploadData(registeredName, file, fee) - if (!transactionBytes || transactionBytes.error) { - throw new Error(transactionBytes?.message || 'Error when uploading') - } else if (transactionBytes.includes('Error 500 Internal Server Error')) { - throw new Error('Error when uploading') - } + let fee = null; - let signAndProcessRes + if (withFee && feeAmount) { + fee = feeAmount; + } else if (withFee) { + const res = await getArbitraryFee(); + if (res.fee) { + fee = res.fee; + } else { + throw new Error('unable to get fee'); + } + } - if (withFee) { - signAndProcessRes = await signAndProcessWithFee(transactionBytes) - } + let transactionBytes = await uploadData(registeredName, file, fee); + if (!transactionBytes || transactionBytes.error) { + throw new Error(transactionBytes?.message || 'Error when uploading'); + } else if (transactionBytes.includes('Error 500 Internal Server Error')) { + throw new Error('Error when uploading'); + } - if (signAndProcessRes?.error) { - throw new Error('Error when signing') - } + let signAndProcessRes; - return signAndProcessRes - } + if (withFee) { + signAndProcessRes = await signAndProcessWithFee(transactionBytes); + } - const uploadData = async (registeredName: string, file:any, fee: number) => { + if (signAndProcessRes?.error) { + throw new Error('Error when signing'); + } - let postBody = '' - let urlSuffix = '' + return signAndProcessRes; + }; - if (file != null) { - // If we're sending zipped data, make sure to use the /zip version of the POST /arbitrary/* API - if (uploadType === 'zip') { - urlSuffix = '/zip' - } + const uploadData = async (registeredName: string, file: any, fee: number) => { + let postBody = ''; + let urlSuffix = ''; - // If we're sending file data, use the /base64 version of the POST /arbitrary/* API - else if (uploadType === 'file') { - urlSuffix = '/base64' - } + if (file != null) { + // If we're sending zipped data, make sure to use the /zip version of the POST /arbitrary/* API + if (uploadType === 'zip') { + urlSuffix = '/zip'; + } - // Base64 encode the file to work around compatibility issues between javascript and java byte arrays - if (isBase64) { - postBody = file - } + // If we're sending file data, use the /base64 version of the POST /arbitrary/* API + else if (uploadType === 'file') { + urlSuffix = '/base64'; + } - if (!isBase64) { - let fileBuffer = new Uint8Array(await file.arrayBuffer()) - postBody = Buffer.from(fileBuffer).toString("base64") - } + // Base64 encode the file to work around compatibility issues between javascript and java byte arrays + if (isBase64) { + postBody = file; + } - } - - let uploadDataUrl = `/arbitrary/${service}/${registeredName}${urlSuffix}` - if (identifier?.trim().length > 0) { - uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}${urlSuffix}` - } - - uploadDataUrl = uploadDataUrl + `?fee=${fee}` - + if (!isBase64) { + let fileBuffer = new Uint8Array(await file.arrayBuffer()); + postBody = Buffer.from(fileBuffer).toString('base64'); + } + } - if (filename != null && filename != 'undefined') { - uploadDataUrl = uploadDataUrl + '&filename=' + encodeURIComponent(filename) - } + let uploadDataUrl = `/arbitrary/${service}/${registeredName}${urlSuffix}`; + if (identifier?.trim().length > 0) { + uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}${urlSuffix}`; + } - if (title != null && title != 'undefined') { - uploadDataUrl = uploadDataUrl + '&title=' + encodeURIComponent(title) - } + uploadDataUrl = uploadDataUrl + `?fee=${fee}`; - if (description != null && description != 'undefined') { - uploadDataUrl = uploadDataUrl + '&description=' + encodeURIComponent(description) - } + if (filename != null && filename != 'undefined') { + uploadDataUrl = + uploadDataUrl + '&filename=' + encodeURIComponent(filename); + } - if (category != null && category != 'undefined') { - uploadDataUrl = uploadDataUrl + '&category=' + encodeURIComponent(category) - } + if (title != null && title != 'undefined') { + uploadDataUrl = uploadDataUrl + '&title=' + encodeURIComponent(title); + } - if (tag1 != null && tag1 != 'undefined') { - uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag1) - } + if (description != null && description != 'undefined') { + uploadDataUrl = + uploadDataUrl + '&description=' + encodeURIComponent(description); + } - if (tag2 != null && tag2 != 'undefined') { - uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag2) - } + if (category != null && category != 'undefined') { + uploadDataUrl = + uploadDataUrl + '&category=' + encodeURIComponent(category); + } - if (tag3 != null && tag3 != 'undefined') { - uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag3) - } + if (tag1 != null && tag1 != 'undefined') { + uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag1); + } - if (tag4 != null && tag4 != 'undefined') { - uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag4) - } + if (tag2 != null && tag2 != 'undefined') { + uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag2); + } - if (tag5 != null && tag5 != 'undefined') { - uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag5) - } + if (tag3 != null && tag3 != 'undefined') { + uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag3); + } - return await reusablePost(uploadDataUrl, postBody) - - } + if (tag4 != null && tag4 != 'undefined') { + uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag4); + } - try { - return await validate() - } catch (error: any) { - throw new Error(error?.message) - } -} \ No newline at end of file + if (tag5 != null && tag5 != 'undefined') { + uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag5); + } + + return await reusablePost(uploadDataUrl, postBody); + }; + + try { + return await validate(); + } catch (error: any) { + throw new Error(error?.message); + } +}; diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts index 477320f..9bb6ca7 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -37,11 +37,8 @@ import { getAssetInfo, getPublicKey, transferAsset, -} from '../background'; -import { - getNameInfo, - uint8ArrayToObject, -} from '../backgroundFunctions/encryption'; +} from '../background/background.ts'; +import { getNameInfo, uint8ArrayToObject } from '../encryption/encryption.ts'; import { showSaveFilePicker } from '../hooks/useQortalMessageListener'; import { getPublishesFromAdminsAdminSpace } from '../components/Chat/AdminSpaceInner'; import { extractComponents } from '../components/Chat/MessageDisplay'; @@ -52,9 +49,9 @@ import { validateSecretKey, } from '../components/Group/Group'; import { QORT_DECIMALS } from '../constants/constants'; -import Base58 from '../deps/Base58'; -import ed2curve from '../deps/ed2curve'; -import nacl from '../deps/nacl-fast'; +import Base58 from '../encryption/Base58.ts'; +import ed2curve from '../encryption/ed2curve.ts'; +import nacl from '../encryption/nacl-fast.ts'; import { base64ToUint8Array, createSymmetricKeyAndNonce, @@ -73,7 +70,7 @@ import { getPermission, isRunningGateway, setPermission, -} from '../qortalRequests'; +} from './qortalRequests.ts'; import TradeBotCreateRequest from '../transactions/TradeBotCreateRequest'; import DeleteTradeOffer from '../transactions/TradeBotDeleteRequest'; import signTradeBotTransaction from '../transactions/signTradeBotTransaction'; diff --git a/src/qortalRequests.ts b/src/qortalRequests/qortalRequests.ts similarity index 99% rename from src/qortalRequests.ts rename to src/qortalRequests/qortalRequests.ts index 08a7c4f..9681790 100644 --- a/src/qortalRequests.ts +++ b/src/qortalRequests/qortalRequests.ts @@ -1,5 +1,5 @@ -import { gateways, getApiKeyFromStorage } from './background'; -import { listOfAllQortalRequests } from './hooks/useQortalMessageListener'; +import { gateways, getApiKeyFromStorage } from '../background/background.ts'; +import { listOfAllQortalRequests } from '../hooks/useQortalMessageListener.tsx'; import { addForeignServer, addGroupAdminRequest, @@ -63,9 +63,9 @@ import { signForeignFees, multiPaymentWithPrivateData, transferAssetRequest, -} from './qortalRequests/get'; -import { getData, storeData } from './utils/chromeStorage'; -import { executeEvent } from './utils/events'; +} from './get.ts'; +import { getData, storeData } from '../utils/chromeStorage.ts'; +import { executeEvent } from '../utils/events.ts'; function getLocalStorage(key) { return getData(key).catch((error) => { diff --git a/src/transactions/ChatBase.ts b/src/transactions/ChatBase.ts index c88d634..17cb509 100644 --- a/src/transactions/ChatBase.ts +++ b/src/transactions/ChatBase.ts @@ -1,8 +1,8 @@ // @ts-nocheck import { QORT_DECIMALS, TX_TYPES } from '../constants/constants'; -import nacl from '../deps/nacl-fast'; -import Base58 from '../deps/Base58'; +import nacl from '../encryption/nacl-fast'; +import Base58 from '../encryption/Base58'; import utils from '../utils/utils'; export default class ChatBase { static get utils() { diff --git a/src/transactions/ChatTransaction.ts b/src/transactions/ChatTransaction.ts index fc67531..7abf040 100644 --- a/src/transactions/ChatTransaction.ts +++ b/src/transactions/ChatTransaction.ts @@ -1,8 +1,8 @@ // @ts-nocheck import ChatBase from './ChatBase'; -import nacl from '../deps/nacl-fast'; -import ed2curve from '../deps/ed2curve'; +import nacl from '../encryption/nacl-fast'; +import ed2curve from '../encryption/ed2curve'; import { Sha256 } from 'asmcrypto.js'; import { CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP } from '../constants/constants'; export default class ChatTransaction extends ChatBase { diff --git a/src/transactions/RemoveRewardShareTransaction.ts b/src/transactions/RemoveRewardShareTransaction.ts index c91d389..078c8ed 100644 --- a/src/transactions/RemoveRewardShareTransaction.ts +++ b/src/transactions/RemoveRewardShareTransaction.ts @@ -1,7 +1,7 @@ // @ts-nocheck import { DYNAMIC_FEE_TIMESTAMP } from '../constants/constants'; -import Base58 from '../deps/Base58'; +import Base58 from '../encryption/Base58'; import publicKeyToAddress from '../utils/generateWallet/publicKeyToAddress'; import TransactionBase from './TransactionBase'; export default class RemoveRewardShareTransaction extends TransactionBase { diff --git a/src/transactions/RewardShareTransaction.ts b/src/transactions/RewardShareTransaction.ts index 67131b3..661f7f8 100644 --- a/src/transactions/RewardShareTransaction.ts +++ b/src/transactions/RewardShareTransaction.ts @@ -2,8 +2,8 @@ import TransactionBase from './TransactionBase'; import { Sha256 } from 'asmcrypto.js'; -import nacl from '../deps/nacl-fast'; -import ed2curve from '../deps/ed2curve'; +import nacl from '../encryption/nacl-fast'; +import ed2curve from '../encryption/ed2curve'; import { DYNAMIC_FEE_TIMESTAMP } from '../constants/constants'; import publicKeyToAddress from '../utils/generateWallet/publicKeyToAddress'; export default class RewardShareTransaction extends TransactionBase { diff --git a/src/transactions/TransactionBase.ts b/src/transactions/TransactionBase.ts index 055a08b..f4abf0f 100644 --- a/src/transactions/TransactionBase.ts +++ b/src/transactions/TransactionBase.ts @@ -1,7 +1,7 @@ // @ts-nocheck -import nacl from '../deps/nacl-fast'; -import Base58 from '../deps/Base58'; +import nacl from '../encryption/nacl-fast.js'; +import Base58 from '../encryption/Base58.js'; import utils from '../utils/utils'; import { QORT_DECIMALS, TX_TYPES } from '../constants/constants.js'; export default class TransactionBase { diff --git a/src/transactions/signChat.ts b/src/transactions/signChat.ts index 5995128..bdc3cb9 100644 --- a/src/transactions/signChat.ts +++ b/src/transactions/signChat.ts @@ -1,6 +1,6 @@ // @ts-nocheck -import nacl from '../deps/nacl-fast'; +import nacl from '../encryption/nacl-fast'; import utils from '../utils/utils'; export const signChat = (chatBytes, nonce, keyPair) => { diff --git a/src/transactions/signTradeBotTransaction.ts b/src/transactions/signTradeBotTransaction.ts index 31b7430..fc0e0e5 100644 --- a/src/transactions/signTradeBotTransaction.ts +++ b/src/transactions/signTradeBotTransaction.ts @@ -1,7 +1,7 @@ // @ts-nocheck -import nacl from '../deps/nacl-fast'; -import Base58 from '../deps/Base58'; +import nacl from '../encryption/nacl-fast'; +import Base58 from '../encryption/Base58'; import utils from '../utils/utils'; const signTradeBotTransaction = async (unsignedTxn, keyPair) => { diff --git a/src/utils/decryptChatMessage.ts b/src/utils/decryptChatMessage.ts index 39085e8..d58009f 100644 --- a/src/utils/decryptChatMessage.ts +++ b/src/utils/decryptChatMessage.ts @@ -1,8 +1,8 @@ // @ts-nocheck -import Base58 from '../deps/Base58'; -import ed2curve from '../deps/ed2curve'; -import nacl from '../deps/nacl-fast'; +import Base58 from '../encryption/Base58'; +import ed2curve from '../encryption/ed2curve'; +import nacl from '../encryption/nacl-fast'; import { Sha256 } from 'asmcrypto.js'; export const decryptChatMessage = ( diff --git a/src/utils/decryptWallet.ts b/src/utils/decryptWallet.ts index bf27e8a..3fa002a 100644 --- a/src/utils/decryptWallet.ts +++ b/src/utils/decryptWallet.ts @@ -1,9 +1,9 @@ // @ts-nocheck import { crypto } from '../constants/decryptWallet'; -import Base58 from '../deps/Base58'; +import Base58 from '../encryption/Base58'; import { AES_CBC, HmacSha512 } from 'asmcrypto.js'; -import { doInitWorkers, kdf } from '../deps/kdf'; +import { doInitWorkers, kdf } from '../encryption/kdf'; import i18n from 'i18next'; export const decryptStoredWallet = async (password, wallet) => { diff --git a/src/utils/generateWallet/generateWallet.ts b/src/utils/generateWallet/generateWallet.ts index ee3a683..977f903 100644 --- a/src/utils/generateWallet/generateWallet.ts +++ b/src/utils/generateWallet/generateWallet.ts @@ -1,7 +1,7 @@ // @ts-nocheck import { crypto, walletVersion } from '../../constants/decryptWallet'; -import { doInitWorkers, kdf } from '../../deps/kdf'; +import { doInitWorkers, kdf } from '../../encryption/kdf'; import PhraseWallet from './phrase-wallet'; import * as WORDLISTS from './wordlists'; import FileSaver from 'file-saver'; diff --git a/src/utils/generateWallet/phrase-wallet.ts b/src/utils/generateWallet/phrase-wallet.ts index 0f21cba..3cd9ed1 100644 --- a/src/utils/generateWallet/phrase-wallet.ts +++ b/src/utils/generateWallet/phrase-wallet.ts @@ -3,208 +3,206 @@ Copyright 2017-2018 @ irontiga and vbcs (original developer) */ -import Base58 from '../../deps/Base58' -import {Sha256, Sha512} from 'asmcrypto.js' -import nacl from '../../deps/nacl-fast' -import utils from '../../utils/utils' +import Base58 from '../../encryption/Base58.js'; +import { Sha256, Sha512 } from 'asmcrypto.js'; +import nacl from '../../encryption/nacl-fast.js'; +import utils from '../../utils/utils'; -import {generateSaveWalletData} from './storeWallet.js' +import { generateSaveWalletData } from './storeWallet.js'; -import publicKeyToAddress from './publicKeyToAddress' -import AltcoinHDWallet from "../../deps/AltcoinHDWallet" +import publicKeyToAddress from './publicKeyToAddress'; +import AltcoinHDWallet from '../../encryption/AltcoinHDWallet.js'; export default class PhraseWallet { - constructor(seed, walletVersion) { + constructor(seed, walletVersion) { + this._walletVersion = walletVersion || 2; + this.seed = seed; - this._walletVersion = walletVersion || 2 - this.seed = seed + this.savedSeedData = {}; + this.hasBeenSaved = false; + } - this.savedSeedData = {} - this.hasBeenSaved = false - } + set seed(seed) { + this._byteSeed = seed; + this._base58Seed = Base58.encode(seed); - set seed(seed) { - this._byteSeed = seed - this._base58Seed = Base58.encode(seed) + this._addresses = []; - this._addresses = [] + this.genAddress(0); + } - this.genAddress(0) - } + getAddress(nonce) { + return this._addresses[nonce]; + } - getAddress(nonce) { - return this._addresses[nonce] - } + get addresses() { + return this._addresses; + } - get addresses() { - return this._addresses - } + get addressIDs() { + return this._addresses.map((addr) => { + return addr.address; + }); + } - get addressIDs() { - return this._addresses.map(addr => { - return addr.address - }) - } + get seed() { + return this._byteSeed; + } - get seed() { - return this._byteSeed - } + addressExists(nonce) { + return this._addresses[nonce] != undefined; + } - addressExists(nonce) { - return this._addresses[nonce] != undefined - } + _genAddressSeed(seed) { + let newSeed = new Sha512().process(seed).finish().result; + newSeed = new Sha512() + .process(utils.appendBuffer(newSeed, seed)) + .finish().result; + return newSeed; + } - _genAddressSeed(seed) { - let newSeed = new Sha512().process(seed).finish().result - newSeed = new Sha512().process(utils.appendBuffer(newSeed, seed)).finish().result - return newSeed - } + genAddress(nonce) { + if (nonce >= this._addresses.length) { + this._addresses.length = nonce + 1; + } - genAddress(nonce) { - if (nonce >= this._addresses.length) { - this._addresses.length = nonce + 1 - } + if (this.addressExists(nonce)) { + return this.addresses[nonce]; + } - if (this.addressExists(nonce)) { - return this.addresses[nonce] - } + const nonceBytes = utils.int32ToBytes(nonce); - const nonceBytes = utils.int32ToBytes(nonce) + let addrSeed = new Uint8Array(); + addrSeed = utils.appendBuffer(addrSeed, nonceBytes); + addrSeed = utils.appendBuffer(addrSeed, this._byteSeed); + addrSeed = utils.appendBuffer(addrSeed, nonceBytes); - let addrSeed = new Uint8Array() - addrSeed = utils.appendBuffer(addrSeed, nonceBytes) - addrSeed = utils.appendBuffer(addrSeed, this._byteSeed) - addrSeed = utils.appendBuffer(addrSeed, nonceBytes) + if (this._walletVersion == 1) { + addrSeed = new Sha256() + .process(new Sha256().process(addrSeed).finish().result) + .finish().result; - if (this._walletVersion == 1) { - addrSeed = new Sha256().process( - new Sha256() - .process(addrSeed) - .finish() - .result - ).finish().result + addrSeed = this._byteSeed; + } else { + addrSeed = this._genAddressSeed(addrSeed).slice(0, 32); + } - addrSeed = this._byteSeed - } else { - addrSeed = this._genAddressSeed(addrSeed).slice(0, 32) - } + const addrKeyPair = nacl.sign.keyPair.fromSeed(new Uint8Array(addrSeed)); - const addrKeyPair = nacl.sign.keyPair.fromSeed(new Uint8Array(addrSeed)); + const address = publicKeyToAddress(addrKeyPair.publicKey); + const qoraAddress = publicKeyToAddress(addrKeyPair.publicKey, true); - const address = publicKeyToAddress(addrKeyPair.publicKey); - const qoraAddress = publicKeyToAddress(addrKeyPair.publicKey, true); + // Create Bitcoin HD Wallet + const btcSeed = [...addrSeed]; + const btcWallet = new AltcoinHDWallet({ + mainnet: { + private: 0x0488ade4, + public: 0x0488b21e, + prefix: 0, + }, + testnet: { + private: 0x04358394, + public: 0x043587cf, + prefix: 0x6f, + }, + }).createWallet(new Uint8Array(btcSeed), false); - // Create Bitcoin HD Wallet - const btcSeed = [...addrSeed]; - const btcWallet = new AltcoinHDWallet({ - mainnet: { - private: 0x0488ADE4, - public: 0x0488B21E, - prefix: 0 - }, - testnet: { - private: 0x04358394, - public: 0x043587CF, - prefix: 0x6F - } - }).createWallet(new Uint8Array(btcSeed), false); + // Create Litecoin HD Wallet + const ltcSeed = [...addrSeed]; + const ltcWallet = new AltcoinHDWallet({ + mainnet: { + private: 0x0488ade4, + public: 0x0488b21e, + prefix: 0x30, + }, + testnet: { + private: 0x04358394, + public: 0x043587cf, + prefix: 0x6f, + }, + }).createWallet(new Uint8Array(ltcSeed), false, 'LTC'); - // Create Litecoin HD Wallet - const ltcSeed = [...addrSeed]; - const ltcWallet = new AltcoinHDWallet({ - mainnet: { - private: 0x0488ADE4, - public: 0x0488B21E, - prefix: 0x30 - }, - testnet: { - private: 0x04358394, - public: 0x043587CF, - prefix: 0x6F - } - }).createWallet(new Uint8Array(ltcSeed), false, 'LTC'); + // Create Dogecoin HD Wallet + const dogeSeed = [...addrSeed]; + const dogeWallet = new AltcoinHDWallet({ + mainnet: { + private: 0x02fac398, + public: 0x02facafd, + prefix: 0x1e, + }, + testnet: { + private: 0x04358394, + public: 0x043587cf, + prefix: 0x71, + }, + }).createWallet(new Uint8Array(dogeSeed), false, 'DOGE'); - // Create Dogecoin HD Wallet - const dogeSeed = [...addrSeed]; - const dogeWallet = new AltcoinHDWallet({ - mainnet: { - private: 0x02FAC398, - public: 0x02FACAFD, - prefix: 0x1E - }, - testnet: { - private: 0x04358394, - public: 0x043587CF, - prefix: 0x71 - } - }).createWallet(new Uint8Array(dogeSeed), false, 'DOGE'); + // Create Digibyte HD Wallet + const dgbSeed = [...addrSeed]; + const dgbWallet = new AltcoinHDWallet({ + mainnet: { + private: 0x0488ade4, + public: 0x0488b21e, + prefix: 0x1e, + }, + testnet: { + private: 0x04358394, + public: 0x043587cf, + prefix: 0x7e, + }, + }).createWallet(new Uint8Array(dgbSeed), false, 'DGB'); - // Create Digibyte HD Wallet - const dgbSeed = [...addrSeed]; - const dgbWallet = new AltcoinHDWallet({ - mainnet: { - private: 0x0488ADE4, - public: 0x0488B21E, - prefix: 0x1E - }, - testnet: { - private: 0x04358394, - public: 0x043587CF, - prefix: 0x7E - } - }).createWallet(new Uint8Array(dgbSeed), false, 'DGB'); + // Create Ravencoin HD Wallet + const rvnSeed = [...addrSeed]; + const rvnWallet = new AltcoinHDWallet({ + mainnet: { + private: 0x0488ade4, + public: 0x0488b21e, + prefix: 0x3c, + }, + testnet: { + private: 0x04358394, + public: 0x043587cf, + prefix: 0x6f, + }, + }).createWallet(new Uint8Array(rvnSeed), false, 'RVN'); - // Create Ravencoin HD Wallet - const rvnSeed = [...addrSeed]; - const rvnWallet = new AltcoinHDWallet({ - mainnet: { - private: 0x0488ADE4, - public: 0x0488B21E, - prefix: 0x3C - }, - testnet: { - private: 0x04358394, - public: 0x043587CF, - prefix: 0x6F - } - }).createWallet(new Uint8Array(rvnSeed), false, 'RVN'); + // Create Pirate Chain HD Wallet + const arrrSeed = [...addrSeed]; + const arrrWallet = new AltcoinHDWallet({ + mainnet: { + private: 0x0488ade4, + public: 0x0488b21e, + prefix: [0x16, 0x9a], + }, + testnet: { + private: 0x04358394, + public: 0x043587cf, + prefix: [0x14, 0x51], + }, + }).createWallet(new Uint8Array(arrrSeed), false, 'ARRR'); - // Create Pirate Chain HD Wallet - const arrrSeed = [...addrSeed]; - const arrrWallet = new AltcoinHDWallet({ - mainnet: { - private: 0x0488ADE4, - public: 0x0488B21E, - prefix: [0x16, 0x9A] - }, - testnet: { - private: 0x04358394, - public: 0x043587CF, - prefix: [0x14, 0x51] - } - }).createWallet(new Uint8Array(arrrSeed), false, 'ARRR'); + this._addresses[nonce] = { + address, + btcWallet, + ltcWallet, + dogeWallet, + dgbWallet, + rvnWallet, + arrrWallet, + qoraAddress, + keyPair: { + publicKey: addrKeyPair.publicKey, + privateKey: addrKeyPair.secretKey, + }, + base58PublicKey: Base58.encode(addrKeyPair.publicKey), + seed: addrSeed, + nonce: nonce, + }; + return this._addresses[nonce]; + } - this._addresses[nonce] = { - address, - btcWallet, - ltcWallet, - dogeWallet, - dgbWallet, - rvnWallet, - arrrWallet, - qoraAddress, - keyPair: { - publicKey: addrKeyPair.publicKey, - privateKey: addrKeyPair.secretKey - }, - base58PublicKey: Base58.encode(addrKeyPair.publicKey), - seed: addrSeed, - nonce: nonce - } - return this._addresses[nonce] - } - - generateSaveWalletData(...args) { - return generateSaveWalletData(this, ...args) - } + generateSaveWalletData(...args) { + return generateSaveWalletData(this, ...args); + } } diff --git a/src/utils/generateWallet/publicKeyToAddress.ts b/src/utils/generateWallet/publicKeyToAddress.ts index ef91420..dba25a1 100644 --- a/src/utils/generateWallet/publicKeyToAddress.ts +++ b/src/utils/generateWallet/publicKeyToAddress.ts @@ -1,7 +1,7 @@ // @ts-nocheck -import Base58 from '../../deps/Base58'; -import BROKEN_RIPEMD160 from '../../deps/broken-ripemd160'; -import RIPEMD160 from '../../deps/ripemd160'; +import Base58 from '../../encryption/Base58.js'; +import BROKEN_RIPEMD160 from '../../encryption/broken-ripemd160.js'; +import RIPEMD160 from '../../encryption/ripemd160.js'; import utils from '../../utils/utils'; import { Buffer } from 'buffer'; import { Sha256 } from 'asmcrypto.js'; diff --git a/src/utils/generateWallet/storeWallet.ts b/src/utils/generateWallet/storeWallet.ts index 458543f..c90b549 100644 --- a/src/utils/generateWallet/storeWallet.ts +++ b/src/utils/generateWallet/storeWallet.ts @@ -1,8 +1,8 @@ // @ts-nocheck import { AES_CBC, HmacSha512 } from 'asmcrypto.js'; -import Base58 from '../../deps/Base58'; -import { doInitWorkers, kdf } from '../../deps/kdf.js'; +import Base58 from '../../encryption/Base58.js'; +import { doInitWorkers, kdf } from '../../encryption/kdf.js'; import { crypto as cryptoVals } from '../../constants/decryptWallet.js'; const getRandomValues = crypto diff --git a/src/utils/validateAddress.ts b/src/utils/validateAddress.ts index 4238311..aa01af1 100644 --- a/src/utils/validateAddress.ts +++ b/src/utils/validateAddress.ts @@ -1,6 +1,6 @@ // @ts-nocheck -import Base58 from '../deps/Base58'; +import Base58 from '../encryption/Base58'; export const validateAddress = (address) => { let isAddress = false; From 2af3cec63ec45d795fda63394278a9313a85d4b5 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 11:55:40 +0200 Subject: [PATCH 04/12] Rename file (typo) --- src/background/background.ts | 2 +- src/encryption/encryption.ts | 2 +- src/qdn/publish/{pubish.ts => publish.ts} | 2 +- src/qortalRequests/get.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename src/qdn/publish/{pubish.ts => publish.ts} (99%) diff --git a/src/background/background.ts b/src/background/background.ts index ec55028..075abea 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -11,7 +11,7 @@ import { objectToBase64, } from '../qdn/encryption/group-encryption'; import ChatComputePowWorker from '../chatComputePow.worker.js?worker'; -import { reusableGet } from '../qdn/publish/pubish'; +import { reusableGet } from '../qdn/publish/publish.ts'; import { signChat } from '../transactions/signChat'; import { createTransaction } from '../transactions/transactions'; import { decryptChatMessage } from '../utils/decryptChatMessage'; diff --git a/src/encryption/encryption.ts b/src/encryption/encryption.ts index 2016166..02ccfa2 100644 --- a/src/encryption/encryption.ts +++ b/src/encryption/encryption.ts @@ -6,7 +6,7 @@ import { encryptDataGroup, objectToBase64, } from '../qdn/encryption/group-encryption.ts'; -import { publishData } from '../qdn/publish/pubish.ts'; +import { publishData } from '../qdn/publish/publish.ts'; import { getData } from '../utils/chromeStorage.ts'; import { RequestQueueWithPromise } from '../utils/queue/queue.ts'; diff --git a/src/qdn/publish/pubish.ts b/src/qdn/publish/publish.ts similarity index 99% rename from src/qdn/publish/pubish.ts rename to src/qdn/publish/publish.ts index 201fc0c..46a6759 100644 --- a/src/qdn/publish/pubish.ts +++ b/src/qdn/publish/publish.ts @@ -90,7 +90,7 @@ export const publishData = async ({ keyPair ) => { if (!arbitraryBytesBase58) { - throw new Error('ArbitraryBytesBase58 not defined'); + throw new Error('ArbitraryBytesBase58 not defined'); // TODO translate } if (!keyPair) { diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts index 9bb6ca7..1878d3f 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -65,7 +65,7 @@ import { uint8ArrayStartsWith, uint8ArrayToBase64, } from '../qdn/encryption/group-encryption'; -import { publishData } from '../qdn/publish/pubish'; +import { publishData } from '../qdn/publish/publish.ts'; import { getPermission, isRunningGateway, From a87bec822762a133f634900efb17cdd7855387a5 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 11:59:25 +0200 Subject: [PATCH 05/12] Delete audio and image files (unused) --- public/appsBg.svg | 9 --------- public/msg-not1.wav | Bin 27712 -> 0 bytes public/vite.svg | 1 - 3 files changed, 10 deletions(-) delete mode 100644 public/appsBg.svg delete mode 100644 public/msg-not1.wav delete mode 100644 public/vite.svg diff --git a/public/appsBg.svg b/public/appsBg.svg deleted file mode 100644 index 9775d89..0000000 --- a/public/appsBg.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/public/msg-not1.wav b/public/msg-not1.wav deleted file mode 100644 index 475c2100cf3ab731bb890a9f83d583cbf33d3a02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27712 zcmZU*2V7Iv`#*lO2O(ka4GJotAc_d?RTS%1+={rW)=@_t-Hy6vZQZNIz4x9dLu4Gh$D2ifH zY-;#5in6-Rrnr<96+eIN{HkRXZ>XU^Xy;h9JX1&3?_vw$p7tD7GlNG<9kERQsv+c;)fRgwljk$rs5N zP9eAOT;7?yklfz6_jB&%bj$6YtISj7wH7K0dzXZje6B34e9-Wq;j!|uvRYrGm-Cu= z@2uWiN!=uFTl|0YZw_q=EgVodz~(EPuLe&TG@(4UGFCWOFt`7L{tH^-o8!;MUx=sY zY3Jp~7RSA*H#EO|KY#mf_Bq^4?xMD(Z9}RCR-Mi{pX2b}=6&&_@<-#Y zkG{V5!r=@06YVE%AANB2!{NlkGY(Hbyzq}De)!Okiips=9$$VHJuzZ&$_l1a-Zy`A-XmMmTtZ@1KO z>6c{%%Yv5oT<-Rb`!}fzKP(KK8!~sz)Rj|P#wfi1AS8qlp^i8;T`tj-M$EF+`c68*?U;p^&57D0% ze>NR&IxagWJ=gDY#N{D(2jA^@(eXn1QT8$7bHr!Adf)mLniZN{L7u?I(aP~wm%Ck- z^!pTb~t7gh)uAaYc{`>g1@l6X`7Usw2#Gjaddj6PM(X)6{_){9j zG>!=!(rbuQALl;x{&oJP&gIUdEut;f8P*xzHotA&Ub?;1BFiFcf8xQ!aZg4+`Q_G* zTbnMeyENi_^m+HwZl}FYx}O|=a@5IPr+1z(5gLI4Zl zl~I+(R(-38C1lxIIa+0SqclB`4s!fp~${yciqmq1szK}mUH8|{x&^rHhF&M`K3pBkAdNl;r)h0 z4S5m$D0nO=h(zC z@5fC3YSLFDh7BL~Eb2wn=dgmXydH%;D!fa)dpm|Z+DmLC52+{Avi7Czvm53#l$Dm1 zdgXcLO;4Mi_BioT;)<86ULJjN=*h^3qaU8Sf9(E|`+wdKec1EiH&0eR>Gsn1<+Q|^ ziOC<|f1H{do10UTUBas8)ZbEFR~@F0&_9ZP7Im}hY8UG~`MeLvKCq}9kbqu!1xf;MgB)RCixMGt#8@czIL{gV0}3po}tt=qJ2pb%QEUS>mR7@s&b5lMuR{rXtr*$Ugo;O zHQH~a-`K$Mf#Z9R@7*J!Tg0OQPX^Qut{!}5=-Ht~(9RAyJEVSK-N2&$CH;Tuv#F0h zmdq3*kwO>HL36T>b0|#^;aCN}70hv*m zQD^&~=^qpx623QdM`%`HL10kVu3hhXJoY&2aMHnE>LeY;8^&8?h&P;7ol^xj1~>Mp z>|I$=R9h66J1aLeJtKWg>Q|{PALJiW5>pau-qpSvmpC?2^}gf%=j4LqnzZUPDw~__ zQRq}imvYO_)E=+BD}N~Oq3Nc1L%pV^ieg2#t#4TW?R?Q$10s;*5zO_6PV+I}@`GgNV_xB-@3EdA`e z+h1}!@3znPknfdlm%5b)mj?@bi+Vfub?mDQSBCEg|85Oeh1>L%_N7C)z0-qJg71w zq{-6gWNos0l6TVX4?7X+Nr5RrDO1uWr~RGzPbQsb%o|ZWvUq&exGHs{w$Y|d*0$L2 zjUkhp&b70&x0KjR?0PA*q%F52XY3&a`hOm?E~weE|uTsfw3RAb+&epM;O z$;G|}eg&(tS7tv*f0*u;=8|?c^+M{j)JdsfANzkC`YH0$tjxH~-MKq*PkuiAd0W}m zGW%MG+TJZ8EtlJ`wZ|H#8ihOo?}+42Nv>^{?QxeAE-$=ZdHHws?Ru&E_3i_MBZ43I zdfuyhXxGr?p{qhqg!~n9x#!iM*8{Hw-VL}HkmsA@+sh-wqsFn$(b?MGS}tl69c3S6 z_ti(}J5(JiK{Ky;cg?<$t)*|RfeWU@1O8Sm2HrXS2Wkg+yv zUDo8>$+_JN0}8W>^NNR84z7%@A5)(uPm}-MalYfS@tW~I_b&H@#R-ePHhpd4oEA7S z?nd_tpK2eDKhw2e_sH&>12+Y}33?r*4blho3F;lRsK=5XR|BpD@Vaoi?Dsn2wcK@) zYl8hX`wg=1WDUYbVHmp)dy9UP{!3eN+wUzqTTa#;uj^YGQTbcRPbE7(@BX|he?@*& z?x0*=4l5@yJ0*Kh&W@a@yuo>m1q}sDzbyXppzKN6gX%lggBpi5j%Xdyx<`FLEv5zZ zHtr5?mAG1Lw9;AKaJcPI@NN;^;=0f6ezg0s?j_xdy7dYO z3%JihgVR&+W(Lr{(Sqjt#ce zF4cF+u9q3VFkhTMJAalGhzlC>n)7bvUC)clkISbD*@c^nHWa-pNiIpPcwe!hc1!K` zrprw+%CX9DO+U?7^hkO(cP=+p94nStSz5*0FR_nu33plJvEGC7F?c`od*gSj>*cNw z{Ga<*`j_~3^Y8AzyUV^VKECe0b)HS0TsMx}Hpi`wKiK?i<8J9}xl*u3aF%t3RiH1> zdv<79B(|ei-yrQbA%DLXLKBGCKxm;PHjMPME?in8%`*FfK=Y?m4pDeR1Z`<6kad-4|6uXLD zXL!VVXub4aHGlyNKMTLb&~&~!-z1-8pB%3&ubv*E9?~6h{^#xJ@r zxy1#=t4hBueN_Iq{BqUhs?fU7y8cc5o31OaDb}>FZC|Hbqub5wXRdQEa}z|DMQf#N zq_sA+HmwdShhJQNa#`uV%01CD(Q}^nJnv|qXrFIxYXV>Qa^qQ!NKB>r3edsZ^*vEiX1QT;*9C>iDUmaQ$T8rC#?t|_cZugs`i zf-$x9TIto2>m`dz;!6rkzLdzyq-BcomhvB~eyjSY_Dt>g#>tH<;>#Tf-pgmMX^PcRg~2LyZ&|`98(=rToPS^+(X?J9!(xPPow8_uUIdwr^d6v zqtTY48{bXfw%cW=%W|icPMP+f>~GuLwTY00%jR0dSKoMyU?exQ4* zyU}r}BU|}d87rSC4{Qo)+F8G~eroNE+LzVOs_#`jtctE0RkfvRbJd9IQPo#!F4z23 z_g9@oqoh&Z+|nH08s3`TmeF=elb|_o_{VUAx=P*SKIC2ioKBHUm!!&)Wo0&HHhb;& z+P`vq?YPu=vGX>U-(4(Rg|2EBjmt=vQ7$59fpag%P{(q+GCQe_%;t-tx9qL5Y2i4b#7)AHy9?hE@wloZ?8&G$!=5Wor>hG#ss#>Z9 z)uQUB)sL&WwY=K$y7Ib#jRP7TTO3;^w@z!VZL4T|sClM&Xt-l|OMRfyxT)Ny!so(` zk`0m;S&NKsE4005f7`y=vEFgF^A6{8E*D&!T%BDR7lTWb%U~CkQ;U-Xez#n;yJYv# zI^BAQbemKqZWFr*oCOdDI3h;O&^lTtQ%lv8RFhR@@=|$HQ%aM6gI|NXR$n`}W=74p z>PgiVRi#xH)za$h>Wu2(+R$3BdYAfbjk_Dyw5({k)q1yeV*7-453Q%x%jjYBWp!nB z=XK@rMIzB7$peX#m5Y^^t-I|5`^WYfj%kkboa3G6yUcP)c1d$N=5o{}!#T@&z0-Q9 zLkv{fR_9lqK=q_1X2FP2NoKL_{`wiDk=qfyKan9ni^oumfI>|cPZlv8rhp7$~jwOzzPL)ov&QqPE zoFkq0I~{VG={U{tr2TpOm9|T4ldRIL(kxRge-!^DrujzxM9w76Ipz#gtFPBj*Tia? z+M3$ZTQgee7JbW6_>p;`;dFyfeL#I!U2xs&+Qi!G+REB}b^Gg{*T1M=(iq>kt$A0o zR3TE-C>xbl9hMz)wez$^h9bi(Y6g|VDd6-M^c5@=FA>kOoMEYvX=Q_L2H9BI+1PEg zUuQqrVYD^-S?nalGYNOLkLM(=UzN8m~4aG>mMBZdl&1tbyOiYjkh& zYP#H<(0p8eQm$x~w;pUe(DuE0v-*hcpze(^(I{r|Sy9|U+y#O-!36OHvDnhWa=Gjq z*(U2P*3Pz$wqxwZ+Oh2^`$6`D?c41->>?p<(KfUV+B;e{N>(f>m2?*eh$9410vM#Y zEmSM@-1yjdME9reu==3-O{*_!?O1N!OoOu8ql2dj;v;Uw_S^Sg-rMc-Mh zwb*F6!7@zNM|Q~Spw&U^!`8tzJ#1#%%(n@$39#8|z0dlf)gG&!vM||p%U>;bTI{kI zCz>E?;WzMS!d&46>nY2Dabh+aHX1bA4((C(arH|04f=~}hpM8rr1gm6sN#(Ll$kbFUba1No7FM9BSaIT>80zb8*BL5@IAeOzCqoj zirHV-Dz1vF;J5N$Kn$HJo+-X>@z5e$k}f%6dD_xl>L!hrj+XjCyK8yJQZ8wcR9e(n z9EVjrYmvQZo?w>XXWlQo-4Ne@Vg1DVj#!jR{z>7fnO&e6@%UDw~zTN^EnKhVF> zDn`M?vEo_D?DuRBt~YlIZ!&KUtZl{#;sv3?Uc!&UG+~6Ozi72+m1qOBXlS*TL;X{Iks;q8 zp=I<)W(2d9`jxuNdc>+@SFn{FHAl^D=VtS=czgJJ`CbAa!A`+eL89QJpg@o*cp`Wy zSO;@rj({&%!C%V1#=Ffch@^&YkO$@waprZCSQ}QY1A}p{I!1C?b=;hYn`R;mhP@@Bg_xJ27E>t z2O1;k2zmfBkQqh|qoP@(ScBQa*&&=jj(}^){m4z^uHdcZwe#e#oH(6-l>Y~4?gsxX z{~Tz0HQy218Qxi*h$rMtokt7x53YjiTY8pj&O z7>?+V>NUD{-FnciGjLp>E!4iyzSO?gzSSzUN^O)bQg=yrS=UP+qR-do>h~EA7-k!1 z7(?hjv@>JRXc?NRq)Mo#te32V?0xL#+sEO^d551x!C z;Z?#K#Yyfd?hqKuk~qnnL7ah{^X$LbdcfyM)>o`U)B!4o$z?pDEu)vwAB^vfJ&Zxd zzkzn3p_d^?pP~O)@V#M*VTNHM;BdNOwqY}TzHYc_Xfr4cql}}Cw~g0XCP0d!bKe5vQCHvW5L2G5n zS%+AEumV}#Sg)wJFd|Q+YMFXwGxI$l%b9sZ->2i~g|riGM;E}4%uB|L#;wp+8dn*= zF)lH#H-2y2ZQNvrXSFcX$wZg#4`(-XV4re2kKjDC9ICUqGT*9 z))dxc)_&FjRwDRC9W()3z_x|v1kDnfk)>zl0ZQ(%Zm>49egiKY!s4<7tarfU7HTuq zmx`d|OcQg3xy~$SmN0=#FUCM?=_ERZzCvH3chEcOjr8~QxAY2nDZQLtPk%@MN^hf2 z(T`7EEY{wQ*~4}l}_ED?oxkJhd|cvsBzS2sv8wZ@u5{Q70f&419O>4 zVD>Txm`$MHMbKt2GnsMF#xgO?C?=Yj!b}8<}krghM^GTWI`VCNgK$&d>7e`ZRV zHl~%~L7%8of1nUfg;4{jA=FpYa4H6JyN*C(!>`JI76;+0-%OxJ5W`?6ZKhVde$&iObyrr zQB%QG!r3~oNflht1h0*7HIDblDQd?6yrI@azz@G#|>(WPS3k z3*`%u!-MjKr`^9z1hVm=7qlbV6Q)Z*3wf#s)D?r(D*+QZfPx~xL$2w4E?}bo-hDBl zqQt~^18}YY4eEeDJ=jP9y^5jPKy!fR4$a5JPZufxcL+6~HB8v>3R_hgJyh3*m^UEd%~a;i_u5 zt_JRC1gx8_OaqUI9>k5*WGP#)pp8k3-k?c0;KLhu_5uz1ncn-rdoR!&dXgKU*%hw0 z2ff>xd`bj;pp6lgh+jQ$+X~d$L8Hy4CI^kyns}%IJ{p0ea(JR7X5P>j%B0$esz0Z%Q!FCw)ObXf&YMHB{bAA9*L$A2h|{x z!Xz`1zyQ5zfqS8;cOlS~!PCyflLzP$wcG`s-hhJuz=$v4qpL~pT}_BUKKEV^Qfl;mquBm`4t3j?- zxK{-n(!eJV#sUdw6aCv2=(&TvF@gpFT6_7o(TINK3)F+an^1#2fg^n14ScExyn4g;7!6#ZAr3Hl5qonfjHen? zG$K6Km?+kOcF?Y6;8)m+;ECQ;1h18F1wN54YzmMO5!yKxU}VRr{(c#A8< z9vlZyudqyS@*o?NBw~m|79bZFFoM|~^uK7wh^z$J(eII3vk8SQCPX&Cr&f5^4Ciqy zgC$fW9yqp}!zs=r0&CiN8qxe-FTO$b}^|0dRn$B#tL+h&?Q@ zB%+ug8KuQhLtLmW+orbfm!SRyugCC9`KdOt=O)Vl^KAyxAc{Q<`?@)={X4jNh+qcTQ396xbH zLr*6tK-#Dyv55=hMP%X&Kcf~fGN2YQx>3LZ13h8G7=fOR4QV26vsRSwSq+*XXFGr& z998iSBNIj@Vl~7D>IARACt8FH?o)} z7_=I4g!duVOrn8?5gBNCq^vNZqzR%7;ud2+Vird+GMB<{(8m$ys7J&v;us$!CUZ>E z3xFDl+1B7WID%tr#~BySKy1LHTuol$1IgBwF zMesdJL1wae2ImSG3-ErF0I4C5C=1T6P-pnWaaCaAjPQ+|<8?S|!l)<)o(bAf0`w$| z4TNLt87bl{UJn#0ig1iGX|w=xNw`CwMhlQnXeE>n^-Uxuza3D^*eCXcoZ&c)`bNED zHX&*hqa8*`)FpC``b4ebuK^OZh>XZBY5?_&52T6uz^G!@G0`-6HNbhIbCih4f%-*F z;z)zq!`Uv5TsR6MuY@+4TXt#``5|`3Bhp7n%y*(hs5N4dPWz)4kuuunzql};Cz`=4 zFqRoiN0f)49%&%As5O)k`@uWSb|Uk{8IpC!GJu^%a+oiQd5 zPVo+Wc5-c&<9}sEpCL9ee{ZIPxJ9>Z)?nOjl%p%AqJs=toGl)h+GFlz+ zgZk{`8YM(d2nU2FB8kL%@)^AfF^N5rxe-c*GkqNOD3ex@GU1kBkI*2xG2_WBC*p%3 zfZ(5Kq!Yc^6XFB=Lv0h@5#dBfa0)m^DT!8y){!omwV~7~0iqCfVx~*hn6BooQZmL_z` zInqD*hRB8S2_rm8jyl1f5px)qFftSE;k9UC;?vj*u{!DnbxNd18rTc5xfvzIf<)Iu z8syjP;Yf#gy4m{3FH%Cy61|XUgcik~Q9Gzba}WQE8^R%>Nv^{?%+ezlC?$S_ybymT z82aybW*eD%`>zbBJ?s%*39rZta*BFD-U)_CJSC%RC))5n{6zGJdL-6BJQ1mp8|()? zo_Icy)l8GB;uK+H6zdLu^4Y~bi&wa zMicfzDF3f)|7#V3bQ04#5kPzcsi806Jch)2qH82t(h~_ zxmhP>jFMjeuP>m633dr@p!9!r*r{_O5BbK-8=*=tOMIQ!i*S$kB8O(bCy|~+IkVRA zh!3-e{&zgXagBH)evguncx1Md`G|DP9CaehJi?KPNP5E2iTL~f%13;Y;Ge`ceMoM-^Azz4Acp-uWH7Bt5zl+bK5yxweObr8B+-K4lEh=8M|0f3yNS*IZ)`@M zu{RR8$W@(k<7a|nvIdGCPi7uwwk!5Q|4JY)YT4bk_1uQ8*+Y#H+v$Rim6P&%_^h+iSTF#ee5 zCDy+P5p}2s65|QB@OtxHj9{Di zVy8w)WH8&2Xv-|S*&C4Zf30u+WrQ?{?8JwOhm+BQ;0GhWc^oreO(-HITn|9Bp{`Ij z_=?y>gb;d&cKmILyJWbU)&{E$xTb~c54bLYYbW??p8TG}6+I5DB9JvjM_B71>&Li) zjO(4aQ-JFpWZe~i(W7>7-2nT-wG3QG#WiePZNZfxTp_~!AzT5$U$m$ZT(iSp%(!-e z`)5)U74l91s{&{*{2hU-V7Qjl2z}tnJy|Ek)f=(~fnJNN3HGMd87Ejf#=Q<-(~7Yl ztYZhjTCxXxc8Ap^+^a)P;a&!=|7I}R%p2w<^MHBGT!o$3OUy;s-@MA)g5ApJ%nRlt zv?^%$I~-Slf?)-EFziQ8qQ+D6pq6JL6;CaJHk+DDO@*_O)Iihz53aePP77h}8~02f zGWVEk(Cba;{Tg!z`hLtLGVkI0B9opFbGUZv1#8Q=dk_kgaBUvf=)1sry*Hf2l`&j- zvH~e^tsGaOaUHxD6#_g(!M^DPY7XS~%?8fq0Y4Lg$0+y)_s>x4A3)MwkiUStv=Z1i z&Y?5ubM)V^$Go1NP0ypJ&=csn^a9vp{+WIX4RbkH!M<`G*l#_xhGMZetRt+$EPu8? z8}k>$93kgx&N$9W&MMA#@Epz=#Zj_b*=yLV*&kUMEHPZ~5Bv{iBA94;9OShuHlBfu z3XQ%)zYcO$d?3$4uQTc#^>+H%kdaZNuh1VeoHYDu{0Z`bexmpYxycSMa~(=R;;l8@Gd-&B@|yVsBtKP;zPl>`32$ zi~~1=i{YZ~qHex6PCHvOTk~A~Or56wq&}wkQ&XpH(msPs3IQ#lA5-_JA348phC=Sx z4dFH6F7aORDaej`YVq76N&H@XQ+Qi=n0Ju3p7lNJCge2yrv61eL^(`3qj_4hC)D-v zAWw8`)%dCT(^D608Lgv}biPf>y0zFHA+xnq(nSYW0>&V2& z;W0yE%3>>GmGfHX*~Ht#2gU`(Et$D!rpI`%@wJ0%2k#8t5nS$E>CECX91-L?`WAL6 z9Q!`{{qGO%N;U9;oJz{%Q4X7M2Y3$^&t+6e!9`k(W9g5o@=P}Q7URi8; zY|pWQWB(a&X@Izgs7H)LjKd=E){PCD8~(~Yn)~rh+8gy9?VXl{`h>_cL(YskIpk#U z>E5Sn|E~O7bW3u}=cVV%nVEAk>#J+3U+7=!bF4G1?fsqnSN31tUp-nk`t!8HY3t{$ zn>RatPW<8pix(W8eQ0*SN&P1!5C1S6Qeu0*@p$856w<;E%J<5!;y%SQlBXw!J_>oX z^U9tpHs@sLB2Gp@4d&33-OhD8cjL;9D>EO>ezYrjNAkqtsl^$}RAq^asI^3vo_7*#Bt-ULi=vcnmK!??3=Rwt94&xg=dAY^IPvXPc~QfhxSix z->TkKgEEF>B))k2V*jm!x0WU>O?Ywk`Pt$#C1-LjWL=nlE&iI@W0%KMlBXnxe+mDR zu1Hsi`4;>lmm-&t;9kLyP&?>e%)OW;Qv_$XU`!YcO?X9k#rlrj9g7$I z7ZGVuX_^vs$wcKukF^fT&%wMAmL#` z$7S{9?{2TTo%Jm1*)OR-r6zs(_$6JQD#!fCuN_A_rgzKgHm-k6|LRcJA7d@`N z(RgForNx&>4&9UDD0dTQ#p)Nyl1%p0*Yd`GyttETI2yWMtOSY24}n%_0g zE1FmIIpuQ-=Q;cNuXncIDZ5&Hb?xO1m#1Hyc6H0`A8&7c`peU{Bz2Nkp+{kGW6#Dg z$fFx&J=}VZZ=CPt-U+=UhYT3<?lYsy zj4s8t#kM~|-f5`3mprRDw|Ls8sh^g;UiSL!!*>tg+_G{$|5dqnrB?y4|(SZP#M)cqm1 zhXf4^9yWByupw4amQj_x>v{)w3+T4aZL`}d=`!gX7-a{w4s6|9xu_|*E?U2cs1%(?>D{P^!d>513#UYJ`f7pCs$9azNWmS{EIqH&6mbW7r4%MZRx7$ z`hBnUy>^E03QvwqiWChH59l8m5&2W!U;0XVih7RiGQP`m=cmp}iAu5yM%ez!2<3(9 z3)PE1fAiTUQsvokhwpz>2vevt~Fh14l9o+Z^F#r zwdA!V*g42~qTgh{i$VVc-3_}N<`Ur^ai{-{{!=4nM0D@dwa>KR8Nma)M0JUA8S7#r zwU%~+yzV}2z1xawOKYEhdGcj`_JZulkC7j5e7OFhHnA?z;)D1@drC*jj?5jIb3f1h zY*j6*wrRC%O@jRTBjUfrxei$ljowY(6MIbVu`hII=&-R1!F>G7# zufd$Ig0BC#o_B4PDP`YtHgd{3iaVY(yll8rdcAaL-iW-0Pj#Q@WGdy&hu0s*B#lYh zo4Pl3S>`vHI|_Fe?yB5bS>ICM;;MJoZx#G3_|0~Q?H-TcJ$M1)fZaW}_q-Z*BP=4k zU${lMIJ`$#P}q~;N5MjWk^dt1cy}DEHuKl=@;xrw{cZQFXuGJ*s4#9-{;o`~O{<+) zGO=WP-p;(VjHHYOX-m?6{rJ;I&re>TAmnCkFW6S_zAUM1d&Aa-nD(*lhpEHVCyO+T zMGlJ{Dm^Pb-}t}wzZ`TWC_3b;kWHbRL%W3pge(kN9JI`TiT^s!4W3$it^Fd4c#Gjw zH09Us-oC%#XoGFJWw|$uzrNW%*-O$Fr#pUf{xmi{CS8@K%KBVTRPeIwSy@m+uZ9

g&=ve&U*g@HwZ`Caq6e)cHz7-K)q z{)+e?@%PLYW_;V&wg>eO>j#w$DVtp|qu{5kU$g2zRewrP%S!8?9+B>w?Uy~eaCG5n zsLpb2bZsnZ|J?2azB^AcQ=*5|n;*P3dBp}y3kV7B6Z|6NNk~#?YUr4d(IFFp#s%5I zI9Uwz9PM4~2Z^J^MG!-OSN*1XSC?EDS{hUulpm5Gnb|LMU)rIxB_HEHrlsYi-OId@ zS)bpW?^Nbk)>vO(p9b|*F_3%s!6M1xj>BDtonG6$UIn}jFb3;_8$;!x)50c&_3Rzm z``4a7_uSI$hi>0^FZI6dc+W9ik|G%Zd6o(7f46T1yR0tzuBi(qb)2_FJZU(Ii zT@(5+>|xkSXh%c-2$>!@J+RqN?)TjFiEFTRsC5P}mDg1ls7q@}ZW#r2V(vv=Menj- zW^ezr@6-F#7pa?I97;<|P1~KdJ8M(nmcnrrF%`cz?P_|iey;wSGnR8-dS4pnyukUR zPpZ$>?pwQe=@rmx1^g#ONLb&nWuc2hdj^LD-}S%iKh`tGbH06?{deN;#6qZFx~96K zQr5NC%`cr@x+H&j{`$;ynV-@=rXBhC=f{S$+O$iVS2M5XU&=pIdcM@J-n;&>>Y1vX zE}^AjYw-=cYjz_%MtMx`Ig;%pzony1( zC(9&D4$Sp?X@WGbo8L5#t{Pnx@umNl*4&od=b5iF^V4(Ehh+@U5M&Fp)%orD=_Q#Z zFs-c3Q{*a~^)C9oygj@GS%R#^N$#ZdQhJT?AMJmq$K4)^AZ+rW8$E9InB+gf-_G04 zd!+MM&e2vQtqws}`Z2>Fh7zc6F09Y5*OY6^&lR05a?N+mKaq1PXK>EI9CjWjub?o$ za6{>a(o0a2$CY#Bo*FOB9>^m8DE=s3ZM)i505x_)y@zyp@Ialpa=UN?TXlm4gt zzxEsB*UvM`^P1CTC$*K@YMXF}@Hz8@aZo#}TQukE;ME;9gE#AXChk9DMTDk7E-)Aq931pdq9Km?Vam~{dXmVS#TaPpzZCqRPUCrMW|5Pk2 zTTu42^m*yCvZrO^E5}!ks~ul^plN^8bL9)=9_?=JIjCtkEjS_Ym-dj#Z5wRQI{od$ zcjLNg+%@i#Jf?UAxcj+(=d!^C=hzX}gRJ{m^tQkp`2u5}F$C%_q^*{%kD$6~Q|;Q? zU#oUh2`Zrwp^{&Dr0Q_hueHC{zJ(enajUr1QSGQcVLWM!fV}oo;#1;PR;!>kINm*Qc&2ZmDjuZd2VBxh!#UaI|yWXLHcT%F@!Zm|w=91lhZfw9mAgR2x)T&DqWV z_1)^vRiCWhQ2AqJW<^THKb4m&b=BJHi}eZhKehbaGOF#Xw(Ghpx_H(SmZeZ6oGP6z zt+B1L{oZM#Q@U%q>qGYk?(QC59vZ0YoaQpaWtzhjhal^o*6tQ=7Hha`xI3X1;d*p)&evnI3*arB!{Z64vwUm$ZD-^0Mt|+e!U-eJVSV zog?}zT5h$-D$-$~gWN^#66-O|Bg$)#*8#6RUXh*yJ(svGa&vHUayoBw&L&V2B=O;S z^Dsw$TgQ%$d-6N-i}jc5Z&lr@5|<0gD@tlgW|d4USzo%PG^oPAVlvb_zHNNb*jw3G zIY~Q7n?|Kj1BF9{lVuZRSM0Ca8(oYpj0fYf&3n7|a;O8o>V4IOl_{>%T|d6FNc8_V?P&+Fr^Y%3m6HG!|9oRWB=FQT`*;rw0{xFWz0eulR83!P3ym(8`{5 zA$4H&;dILVoO_E`lh+BK6Fz5r&ibglTD@+% z-*(qIX`HNVZEOcfq9l3TTyBIh()gnNX?sRXPRrA}hjkw+Qz{3RMU@SL8twYx`r`hj z5vAT0eifT)Hq?llESh#JcPnpbZ)h>Ue4%imaD;4_Y`Xm{`}r<&U2HsT;pgT!FBLR{ zr_pn>`)2oj&ikB~*e$jjFC8b{CpaM3NPSN&(=OLOS3XrfZFjX{tM_FH|K|-Ke@+#iGJ>lUkuxjDWhj4!uU7!AfIo5^NFNm)w(# zupVJu3-ysdIc|2WaH@5(auz!qKnZW{U)ld+^Q+B#>3r!)(Mb{F)Rl2%8ng{s+jiUb z5=DvPTd3Wm8;lJn>(A8Bt)Epts9{J$NK>ySce%U#qw=HDPu)eGuK%RJ3N^)xcnf(c zqBPMZ%fBsOTP0d)Y}#$Y?ZWNi?H1YXwcTU;*!r-OmOb?okNRk=f*>paDlmLFPnHvbN7ck@oD8=R|%Q`o6&RC7D#bp%0;+W@E?ie=AY zkK@PiOGRa(21%o2AjGzNR*$SESx>MwS{ba~$ll5BTHdm(6E}$ugYEL5o)mLsDFfFq zMKfL_ZMSbiGXODk9{tVCWC zZ!*+RE|M&gWJoimKUi(H8VbK>0<8V5y{&w#hDwJ@&sdzcm@b?lJPonz1a*SiZP;x% zr#Y{Yv|F_Elsx54`E7ZxmcW+%&4-)i&CSgr@(?*Jd9;pci)kxW7pqJ3WqN;D4_wJz z!QCU=Cmb#rDp?>~C`+_{XFc3@xNW-aC);VZGi;l!>#g_8j>uL?zLA^~o)^yJ&f=Cp z?&<{nI6b9isZm$S@)Y^d=3&hj8~jo_R01;?6=zwv>Rw=ur^rdz<9U``bYIo>z_3|X-I7P(DX$9SiTu* z0}Is!>Z69g47*u7S@HbEe1ljk7Rp4jc~H|?YFBJG8Di!ehj$Kk4pt7+>}J{}SYNQV zmfA?ah>Ar%JTG1ltT~42L-lJr)^-Fa15E!f@BnIHtLw|^o$5X6*VHeo&uGYR7}`9b z`FF)G#qqY|ZQZrqwAN5pW6hCp$_2H8>5_?(V5>e>gKeX1ajf{!;U|Y3(0;W4!G5Xj za@*Bb%dIX-u1Y2d#|!`99O2xjAJWy@3hnatZ`)V4u4;{F32(`6%xzo=Rl>3cTlkgn zv|&Zl>L!_7B44BYPPwJyhYp3VMfWFjj2Xloz)cWd5*~y9E0N0-vUN~b>tyF__tNee zc*Hk$?zT?0rB+o|)s~f(L&QVGJ@`HN1+0A5cgD5G0onoDCGFp|dnjF$zsk4EUpGH* z{?b(1RM%A7RNh?Hd`W&)K1?}SxuShlyQ|hwD=>>@ph?mv>A4J-8NeRE{+buVlL>8v z3&iuqJz)HawVZ013IBg_9a@?sO)}qNzD2w!UIe8@f*9^t?rPRb)*gC4{YjsqAFcgb z+e6(=y|sO7`@*)xZB?o=l}2SyU2aQgt7)%mKdwHeexiM%buc&@&e3OR{5^S`bDWdS z%i!G*+!1sWbrHFU-Nn1byG;K(mm*3Q{VhxodJ5bG{dkc)PmVjMnyR5L(h2k)!#=~` zy7M|4t*y35U8MF^d#ejNiaH$CwlLFuqy7Swi!r+KIjP^ItX5ZZ>$qNg z4?fLj_}k$B70wIJ38o0939j%j@z3(k@y3DiIwfbLlzv$w$ z3$>?U-ME*gFZ^>#nC1k~U7}s0-3Il89k8x_(0Ir=otegXv;0`qP#e01`vbR}SHaWr zJNWknj|DbDsgNra2=@y12p;pF@b~d{^BlRZ+|}$A>~m22oI_{OBB)jVR=-@&*70;r zng&f*O@Jl^{_`VCovR+K8KAL-|E7x6^)uDZPBBh0!t{YrQEk)<_A_=I{0~|I)JNj~ z2c!uy1cRV{cbIULuu)JafS>UKOTHEV3O9i(;mA1APd&nwOe4nm16*X`#2$pD-LXMAI>}6!vT2uW(qKIh#3Ju9h3l z59j*{`~;6c#uh;%RI|Sl^b+(G#PG-R9eEDC%bY75E2xQ^1A8Vn=xemhC^a6{AJfm) z&Co5-#%n)lK4`XRHiKr5X?W0KIpj)Ktrw`W;Hw-guHvVY*1T}a8u#@v6tdBisKW3+MaybvUx45yq znY_o)GI*bOr+MdjT_A3*;cnpm&iNJS{m71H4P#-3a3x(%CmWNENrqGdt=H*i>gT|! zJfo}D)#x~So_?u*q28!x8D1Nn8Lq+}#WngO{S@}0-a~caYp5tZ$3DZJ$(hB0zhmQI z-RJ`DLO|tME}zTgZi9Nnp3N@B$N&`DgvD9tmKI6hTFnj2IP*>?hzl1u_-;CRh zOJR=(|5N6q@szRNSYsSPe+AX3Pw9coU?z!4X7G1GI+a5WXANSVXC=VD8P&3!*v=q{ zAKM*{%`6q`KIf8s+k8fCGh+8FZvJoM}xWaV0r)@M)##h zfPXKim%xhTKXfZCr$;cunG3Lwhr6qDsm0V|>Nf0M@mT{{k*vk6g{+NW)8AM>u$Hq{ zv9LDU2`VCUr~;^!+y!-pgJ9pTnK{FpgSy7)P?77!G}BFVI-La-ug~dM^jkU`d{jZJ zXb;96YGEfs)%GD%Z6WTHPJ&&)z0^*qx_wDiQRSd510{rgFbe3a;r}NJs4q~B`HD)KWH_Se0xQkc-JBPRr zj1_SssZronbEtJNf8GtW_k$#dU@dA7eEJRQ)K|ma-E8=NIMi1Lz!>ic`-e){Rjq>R zmt-adDqEjJ1?pAP&SwJiH|&94hSlr4@N4)X*enewWy9>J26kW-uy;;@=UbWTU9bwL zH|#$4g4g~~aT*R4t-Ya+s5k6hV%?4>>{??dMvVEMoGI7P6I zg%xIjdcuI2Y~yaPS88n3*p|V7wqr)n|3;V;G7$L z;{tMGO&FVi4AlWx!-Z8yPVkD=pjeNF`_NXfLu(D1!b(!y$wt{3*yF{_ZmiYBY=5k^ z!5Rvb4{Pdh&m7-lRV3DQ;J!CjPGH_JW+dQlKFK@Bd~U4l!JTt_#VP=d$e8gj`IjPQ z`eOzFR-<4J2WB*21_5RbU|ud(a$)WS))!&EKh}0&-Zoa7U`3`9K9Rk5Qk#R>1DM4@ zst_o?okAd}qXm_k9vx9H3{>c)wfI06Z z=NM~tuyzJ(XfQhgD^qKsnQO%mcUZfK`3qP}i?t6V&jV{_`9KT%#`;Pp6aGlWpBLcW z9Z-Nz7x>;Du9KQLM9X3A1Xk)`%@@)`q?JJ{H&q^*u~%k79#*Yjz5^l;bB8b+gVgC_ zHJ&rjM1*^pD&btf|GSvZVwI;IP_zZQ79cC?nE_j3Ef-c{wm|(0smVpZ!I>yWxuQrUuf#Ohm8 z3yhkr_?MSjlRgmXn0bZ%g{VP96KkR!(aY_CwhJ81xesVSImk*k0sZUoHV3} zn8$vwA@At9=%1MVk5vWe*(5Vj4|LEHSdWHvf9Uy`LnMS(j3tODk|9QVLrF*_48{P= zkHXL9*kI1dLJB0O2z?lR9yLPhkIdC|<`{sQ!mE*Q%xl7|Z<1L=YKbteVKp6zgjn5z zRp2NOMjpHxbNDe@k*I}opmxyaXeUxbM=(HWV?7vp8Rl}aK~A&`Y5?gVHM9)o5VZkS zw3eAR!2(7$L=oPLx5+;lq>33)IC_y9 zHI$8LnV<-5MWQ09%EwwRq>D0RJSVuo(F-%ONCOtZ+N5D#5~&fzx-7&Wp@ni{gg2Nz zqfPM>Ms1R}X2v3^y+Ycgo($iSI8EyR(2A&89KX!jd1TZ;{!wDI6h=4f8)={>5j_~I z5y$2%UhDx;L^9;?8|)FWglNQ1_&v@q2s+6p@|k4Cp%nNTDI&7)E*uHaGtdgC74!j= zkz{0&99SG>aO}b-)`uXkSfPSgBl&K`1OA&0h|(keP~)fpq8%LHuu2Wlfg><_9eNhl z9pl|Z3eqPlBKiHPf@NTLhs7qyP`kWYdZCR)fV+RuzPl!Z{hBle9{No+>{06U?Kn zi0$wlxeI-vlY25QA|=!iQbI4l-bwr+N5ss3{mG06v%RrT64x>QkZ%!vh<3ySK8R#Q z`^YPaapsth=toOntVO*bAE+gBrYutLvtZo7k$+YpRg*}Y zco*?Af_5S~>H@WhI3#*ReG<&$8T0`3aDqXz2Jm{6lUNjWhwl;9s9B7ZVAu6E)OqncTp1eIuIj7TK& znHyr7ixMD@=InLiX`PxvZy>rh$8&-d^a;Wv=^OL4(c2J%h%b}?dHmne0lND4 zfm~q^cnx|EN`goHHG^EDG-fNCeV^EXSiy`}^gc2ZLk`Tj@MbKNYf&b&6Y)nfP9c4> zKa)`csh}PRRT3+R9uNsQN|QP^vxe|~d=UCX$9O-9P@O)E{h3Fj&ggv>##$Dd2rX7Z|J28bq^5;lK2V&k;)hS7tm*_#hfUjv@QSCEkgv5WXXzadc>?=jy?qBN1fj99a5cqFXM`asSQnZ$STdZdY#!>b9$om$3gQO9T- zNhkB?&rhbFX7HxIz#~G(80C#DF~fI6iAQqb&W+AspUQL(gB+!)JX)$?o^)BOWL_4) zK<+@NH@$@0xa2Ka5}Ps{wMIiYv0}15oCnM!pbrN3iN>${GvuZ>%$evtK4ee*6DBztMfb&Y$^MD Ytu@+R+{@*3otInL+T-(HUu}DqFHwX?ZvX%Q diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 4585a2a570373239baa3f48bd0d8b1de6620ceb3 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 12:05:23 +0200 Subject: [PATCH 06/12] Remove empty function clearAllNotifications --- src/background/background-cases.ts | 27 --------------------- src/background/background.ts | 21 +++-------------- src/encryption/encryption.ts | 38 +----------------------------- 3 files changed, 4 insertions(+), 82 deletions(-) diff --git a/src/background/background-cases.ts b/src/background/background-cases.ts index 7c6677e..80ab3ac 100644 --- a/src/background/background-cases.ts +++ b/src/background/background-cases.ts @@ -11,7 +11,6 @@ import { checkLocalFunc, checkNewMessages, checkThreads, - clearAllNotifications, createEndpoint, createGroup, decryptDirectFunc, @@ -1142,32 +1141,6 @@ export async function getEnteredQmailTimestampCase(request, event) { } } -export async function clearAllNotificationsCase(request, event) { - try { - await clearAllNotifications(); - - event.source.postMessage( - { - requestId: request.requestId, - action: 'clearAllNotifications', - payload: true, - type: 'backgroundMessageResponse', - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: 'clearAllNotifications', - error: error?.message, - type: 'backgroundMessageResponse', - }, - event.origin - ); - } -} - export async function setGroupDataCase(request, event) { try { const { groupId, secretKeyData, secretKeyResource, admins } = diff --git a/src/background/background.ts b/src/background/background.ts index 075abea..90b2f2e 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -34,7 +34,6 @@ import { cancelBanCase, cancelInvitationToGroupCase, checkLocalCase, - clearAllNotificationsCase, createGroupCase, createPollCase, createRewardShareCase, @@ -303,6 +302,7 @@ export const createEndpoint = async (endpoint, customApi?: string) => { }; export const walletVersion = 2; + // List of your API endpoints const apiEndpoints = [ 'https://api.qortal.org', @@ -436,10 +436,6 @@ export async function performPowTask(chatBytes, difficulty) { }); } -function playNotificationSound() { - // chrome.runtime.sendMessage({ action: "PLAY_NOTIFICATION_SOUND" }); -} - const handleNotificationDirect = async (directs) => { let isFocused; const wallet = await getSaveWallet(); @@ -603,7 +599,7 @@ export function updateThreadActivity({ threads = JSON.parse(storedData); } - let lastResetTime = threads.lastResetTime || 0; + const lastResetTime = threads.lastResetTime || 0; // Check if a week has passed since the last reset if (currentTime - lastResetTime > ONE_WEEK_IN_MS) { @@ -653,7 +649,7 @@ export function updateThreadActivity({ const handleNotification = async (groups) => { const wallet = await getSaveWallet(); const address = wallet.address0; - let isDisableNotifications = + const isDisableNotifications = (await getUserSettings({ key: 'disable-push-notifications' })) || false; let mutedGroups = (await getUserSettings({ key: 'mutedGroups' })) || []; @@ -678,7 +674,6 @@ const handleNotification = async (groups) => { const newActiveChats = data; const oldActiveChats = await getChatHeads(); - let results = []; let newestLatestTimestamp = null; let oldestLatestTimestamp = null; // Find the latest timestamp from newActiveChats @@ -878,13 +873,6 @@ export async function storeWallets(wallets) { }); } -export async function clearAllNotifications() { - // const notifications = await chrome.notifications.getAll(); - // for (const notificationId of Object.keys(notifications)) { - // await chrome.notifications.clear(notificationId); - // } -} - export async function getUserInfo() { const wallet = await getSaveWallet(); const address = wallet.address0; @@ -3233,9 +3221,6 @@ function setupMessageListener() { case 'addGroupNotificationTimestamp': addGroupNotificationTimestampCase(request, event); break; - case 'clearAllNotifications': - clearAllNotificationsCase(request, event); - break; case 'setGroupData': setGroupDataCase(request, event); break; diff --git a/src/encryption/encryption.ts b/src/encryption/encryption.ts index 02ccfa2..04c3b65 100644 --- a/src/encryption/encryption.ts +++ b/src/encryption/encryption.ts @@ -12,49 +12,13 @@ import { RequestQueueWithPromise } from '../utils/queue/queue.ts'; export const requestQueueGetPublicKeys = new RequestQueueWithPromise(10); -const apiEndpoints = [ - 'https://api.qortal.org', - 'https://api2.qortal.org', - 'https://appnode.qortal.org', - 'https://apinode.qortalnodes.live', - 'https://apinode1.qortalnodes.live', - 'https://apinode2.qortalnodes.live', - 'https://apinode3.qortalnodes.live', - 'https://apinode4.qortalnodes.live', -]; - -async function findUsableApi() { - for (const endpoint of apiEndpoints) { - try { - const response = await fetch(`${endpoint}/admin/status`); - if (!response.ok) throw new Error('Failed to fetch'); - - const data = await response.json(); - if (data.isSynchronizing === false && data.syncPercent === 100) { - console.log(`Usable API found: ${endpoint}`); - return endpoint; - } else { - console.log(`API not ready: ${endpoint}`); - } - } catch (error) { - console.error(`Error checking API ${endpoint}:`, error); - } - } - - throw new Error( - i18n.t('question:message.error.no_api_found', { - postProcess: 'capitalizeFirstChar', - }) - ); -} - async function getSaveWallet() { const res = await getData('walletInfo').catch(() => null); if (res) { return res; } else { - throw new Error('No wallet saved'); + throw new Error('No wallet saved'); // TODO translate } } From ac4500027e84a737ed6302864d51661ed56dfa08 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 12:09:50 +0200 Subject: [PATCH 07/12] Rename folder and file --- src/App.tsx | 2 +- src/background/background-cases.ts | 2 +- src/background/background.ts | 2 +- src/components/Save/Save.tsx | 2 +- src/hooks/useQortalMessageListener.tsx | 2 +- src/qortal/get.ts | 7005 +++++++++++++++++ .../qortal-requests.ts} | 0 src/qortalRequests/get.ts | 32 +- 8 files changed, 7026 insertions(+), 21 deletions(-) create mode 100644 src/qortal/get.ts rename src/{qortalRequests/qortalRequests.ts => qortal/qortal-requests.ts} (100%) diff --git a/src/App.tsx b/src/App.tsx index fe721a7..4fd9057 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -124,7 +124,7 @@ import { Tutorials } from './components/Tutorials/Tutorials'; import { useHandleTutorials } from './hooks/useHandleTutorials.tsx'; import { useHandleUserInfo } from './hooks/useHandleUserInfo.tsx'; import { Minting } from './components/Minting/Minting'; -import { isRunningGateway } from './qortalRequests/qortalRequests.ts'; +import { isRunningGateway } from './qortal/qortal-requests.ts'; import { QMailStatus } from './components/QMailStatus'; import { GlobalActions } from './components/GlobalActions/GlobalActions'; import { useBlockedAddresses } from './hooks/useBlockUsers.tsx'; diff --git a/src/background/background-cases.ts b/src/background/background-cases.ts index 80ab3ac..d94a308 100644 --- a/src/background/background-cases.ts +++ b/src/background/background-cases.ts @@ -65,7 +65,7 @@ import { import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from '../constants/constants'; import Base58 from '../encryption/Base58.ts'; import { encryptSingle } from '../qdn/encryption/group-encryption'; -import { _createPoll, _voteOnPoll } from '../qortalRequests/get'; +import { _createPoll, _voteOnPoll } from '../qortal/get.ts'; import { createTransaction } from '../transactions/transactions'; import { getData, storeData } from '../utils/chromeStorage'; diff --git a/src/background/background.ts b/src/background/background.ts index 90b2f2e..2a7b158 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -1,5 +1,5 @@ // @ts-nocheck -import '../qortalRequests/qortalRequests'; +import '../qortal/qortal-requests.ts'; import { isArray } from 'lodash'; import { uint8ArrayToObject } from '../encryption/encryption.ts'; import Base58 from '../encryption/Base58'; diff --git a/src/components/Save/Save.tsx b/src/components/Save/Save.tsx index 75ed603..ed5c77e 100644 --- a/src/components/Save/Save.tsx +++ b/src/components/Save/Save.tsx @@ -26,7 +26,7 @@ import { IconWrapper } from '../Desktop/DesktopFooter'; import { Spacer } from '../../common/Spacer'; import { LoadingButton } from '@mui/lab'; import { saveToLocalStorage } from '../Apps/AppsNavBarDesktop'; -import { decryptData, encryptData } from '../../qortalRequests/get'; +import { decryptData, encryptData } from '../../qortal/get.ts'; import { saveFileToDiskGeneric } from '../../utils/generateWallet/generateWallet'; import { base64ToUint8Array, diff --git a/src/hooks/useQortalMessageListener.tsx b/src/hooks/useQortalMessageListener.tsx index acda0ca..37ec1c3 100644 --- a/src/hooks/useQortalMessageListener.tsx +++ b/src/hooks/useQortalMessageListener.tsx @@ -2,7 +2,7 @@ import { useCallback, useContext, useEffect, useState } from 'react'; import { executeEvent } from '../utils/events'; import { navigationControllerAtom } from '../atoms/global'; import { Filesystem, Directory } from '@capacitor/filesystem'; -import { saveFile } from '../qortalRequests/get'; +import { saveFile } from '../qortal/get'; import { mimeToExtensionMap } from '../utils/memeTypes'; import { QORTAL_APP_CONTEXT } from '../App'; import FileSaver from 'file-saver'; diff --git a/src/qortal/get.ts b/src/qortal/get.ts new file mode 100644 index 0000000..102a9bf --- /dev/null +++ b/src/qortal/get.ts @@ -0,0 +1,7005 @@ +import { Sha256 } from 'asmcrypto.js'; +import { + createEndpoint, + getBalanceInfo, + getFee, + getKeyPair, + getLastRef, + getSaveWallet, + processTransactionVersion2, + signChatFunc, + joinGroup as joinGroupFunc, + sendQortFee, + sendCoin as sendCoinFunc, + createBuyOrderTx, + performPowTask, + parseErrorResponse, + groupSecretkeys, + registerName, + updateName, + leaveGroup, + inviteToGroup, + getNameInfoForOthers, + kickFromGroup, + banFromGroup, + cancelBan, + makeAdmin, + removeAdmin, + cancelInvitationToGroup, + createGroup, + updateGroup, + sellName, + cancelSellName, + buyName, + getBaseApi, + getAssetBalanceInfo, + getNameOrAddress, + getAssetInfo, + getPublicKey, + transferAsset, +} from '../background/background.ts'; +import { getNameInfo, uint8ArrayToObject } from '../encryption/encryption.ts'; +import { showSaveFilePicker } from '../hooks/useQortalMessageListener.tsx'; +import { getPublishesFromAdminsAdminSpace } from '../components/Chat/AdminSpaceInner.tsx'; +import { extractComponents } from '../components/Chat/MessageDisplay.tsx'; +import { + decryptResource, + getGroupAdmins, + getPublishesFromAdmins, + validateSecretKey, +} from '../components/Group/Group.tsx'; +import { QORT_DECIMALS } from '../constants/constants.ts'; +import Base58 from '../encryption/Base58.ts'; +import ed2curve from '../encryption/ed2curve.ts'; +import nacl from '../encryption/nacl-fast.ts'; +import { + base64ToUint8Array, + createSymmetricKeyAndNonce, + decryptDeprecatedSingle, + decryptGroupDataQortalRequest, + decryptGroupEncryptionWithSharingKey, + decryptSingle, + encryptDataGroup, + encryptSingle, + objectToBase64, + uint8ArrayStartsWith, + uint8ArrayToBase64, +} from '../qdn/encryption/group-encryption.ts'; +import { publishData } from '../qdn/publish/publish.ts'; +import { + getPermission, + isRunningGateway, + setPermission, +} from './qortal-requests.ts'; +import TradeBotCreateRequest from '../transactions/TradeBotCreateRequest.ts'; +import DeleteTradeOffer from '../transactions/TradeBotDeleteRequest.ts'; +import signTradeBotTransaction from '../transactions/signTradeBotTransaction.ts'; +import { createTransaction } from '../transactions/transactions.ts'; +import { executeEvent } from '../utils/events.ts'; +import { fileToBase64 } from '../utils/fileReading/index.ts'; +import { mimeToExtensionMap } from '../utils/memeTypes.ts'; +import { RequestQueueWithPromise } from '../utils/queue/queue.ts'; +import utils from '../utils/utils.ts'; +import ShortUniqueId from 'short-unique-id'; +import { isValidBase64WithDecode } from '../utils/decode.ts'; +import i18n from 'i18next'; + +const uid = new ShortUniqueId({ length: 6 }); + +export const requestQueueGetAtAddresses = new RequestQueueWithPromise(10); + +const sellerForeignFee = { + LITECOIN: { + value: '~0.00005', + ticker: 'LTC', + }, + DOGECOIN: { + value: '~0.005', + ticker: 'DOGE', + }, + BITCOIN: { + value: '~0.0001', + ticker: 'BTC', + }, + DIGIBYTE: { + value: '~0.0005', + ticker: 'DGB', + }, + RAVENCOIN: { + value: '~0.006', + ticker: 'RVN', + }, + PIRATECHAIN: { + value: '~0.0002', + ticker: 'ARRR', + }, +}; + +const btcFeePerByte = 0.000001; +const ltcFeePerByte = 0.0000003; +const dogeFeePerByte = 0.00001; +const dgbFeePerByte = 0.0000001; +const rvnFeePerByte = 0.00001125; + +const MAX_RETRIES = 3; // Set max number of retries + +export async function retryTransaction( + fn, + args, + throwError, + retries = MAX_RETRIES +) { + let attempt = 0; + while (attempt < retries) { + try { + return await fn(...args); + } catch (error) { + console.error(`Attempt ${attempt + 1} failed: ${error.message}`); + attempt++; + if (attempt === retries) { + console.error( + i18n.t('question:message.generic.max_retry_transaction', { + postProcess: 'capitalizeFirstChar', + }) + ); + if (throwError) { + throw new Error( + error?.message || + i18n.t('question:message.error.process_transaction', { + postProcess: 'capitalizeFirstChar', + }) + ); + } else { + throw new Error( + error?.message || + i18n.t('question:message.error.process_transaction', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } + await new Promise((res) => setTimeout(res, 10000)); + } + } +} + +function roundUpToDecimals(number, decimals = 8) { + const factor = Math.pow(10, decimals); // Create a factor based on the number of decimals + return Math.ceil(+number * factor) / factor; +} + +export const _createPoll = async ( + { pollName, pollDescription, options }, + isFromExtension, + skipPermission +) => { + const fee = await getFee('CREATE_POLL'); + let resPermission = {}; + if (!skipPermission) { + resPermission = await getUserPermission( + { + text1: i18n.t('question:request_create_poll', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:poll', { + name: pollName, + postProcess: 'capitalizeFirstChar', + }), + text3: i18n.t('question:description', { + description: pollDescription, + postProcess: 'capitalizeFirstChar', + }), + text4: i18n.t('question:options', { + optionList: options?.join(', '), + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + } + + const { accepted = false } = resPermission; + + if (accepted || skipPermission) { + const wallet = await getSaveWallet(); + const address = wallet.address0; + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey, + }; + let lastRef = await getLastRef(); + + const tx = await createTransaction(8, keyPair, { + fee: fee.fee, + ownerAddress: address, + rPollName: pollName, + rPollDesc: pollDescription, + rOptions: options, + lastReference: lastRef, + }); + const signedBytes = Base58.encode(tx.signedBytes); + const res = await processTransactionVersion2(signedBytes); + if (!res?.signature) + throw new Error( + res?.message || + i18n.t('question:message.error.process_transaction', { + postProcess: 'capitalizeFirstChar', + }) + ); + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +const _deployAt = async ( + { name, description, tags, creationBytes, amount, assetId, atType }, + isFromExtension +) => { + const fee = await getFee('DEPLOY_AT'); + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:deploy_at', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:name', { + name: name, + postProcess: 'capitalizeFirstChar', + }), + text3: i18n.t('question:description', { + description: description, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + + const { accepted } = resPermission; + + if (accepted) { + const wallet = await getSaveWallet(); + const address = wallet.address0; + const lastReference = await getLastRef(); + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey, + }; + + const tx = await createTransaction(16, keyPair, { + fee: fee.fee, + rName: name, + rDescription: description, + rTags: tags, + rAmount: amount, + rAssetId: assetId, + rCreationBytes: creationBytes, + atType: atType, + lastReference: lastReference, + }); + + const signedBytes = Base58.encode(tx.signedBytes); + + const res = await processTransactionVersion2(signedBytes); + if (!res?.signature) + throw new Error( + res?.message || + i18n.t('question:message.error.process_transaction', { + postProcess: 'capitalizeFirstChar', + }) + ); + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const _voteOnPoll = async ( + { pollName, optionIndex, optionName }, + isFromExtension, + skipPermission +) => { + const fee = await getFee('VOTE_ON_POLL'); + let resPermission = {}; + + if (!skipPermission) { + resPermission = await getUserPermission( + { + text1: i18n.t('question:request_vote_poll', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:poll', { + name: pollName, + postProcess: 'capitalizeFirstChar', + }), + text3: i18n.t('question:option', { + option: optionName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + } + + const { accepted = false } = resPermission; + + if (accepted || skipPermission) { + const wallet = await getSaveWallet(); + const address = wallet.address0; + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey, + }; + let lastRef = await getLastRef(); + + const tx = await createTransaction(9, keyPair, { + fee: fee.fee, + voterAddress: address, + rPollName: pollName, + rOptionIndex: optionIndex, + lastReference: lastRef, + }); + const signedBytes = Base58.encode(tx.signedBytes); + const res = await processTransactionVersion2(signedBytes); + if (!res?.signature) + throw new Error( + res?.message || + i18n.t('question:message.error.process_transaction', { + postProcess: 'capitalizeFirstChar', + }) + ); + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +// Map to store resolvers and rejectors by requestId +const fileRequestResolvers = new Map(); + +const handleFileMessage = (event) => { + const { action, requestId, result, error } = event.data; + + if ( + action === 'getFileFromIndexedDBResponse' && + fileRequestResolvers.has(requestId) + ) { + const { resolve, reject } = fileRequestResolvers.get(requestId); + fileRequestResolvers.delete(requestId); // Clean up after resolving + + if (result) { + resolve(result); + } else { + reject( + error || + i18n.t('question:message.error.retrieve_file', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } +}; + +window.addEventListener('message', handleFileMessage); + +function getFileFromContentScript(fileId) { + return new Promise((resolve, reject) => { + const requestId = `getFile_${fileId}_${Date.now()}`; + + fileRequestResolvers.set(requestId, { resolve, reject }); // Store resolvers by requestId + const targetOrigin = window.location.origin; + + // Send the request message + window.postMessage( + { action: 'getFileFromIndexedDB', fileId, requestId }, + targetOrigin + ); + + // Timeout to handle no response scenario + setTimeout(() => { + if (fileRequestResolvers.has(requestId)) { + fileRequestResolvers.get(requestId).reject( + i18n.t('question:message.error.timeout_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + fileRequestResolvers.delete(requestId); // Clean up on timeout + } + }, 10000); // 10-second timeout + }); +} + +const responseResolvers = new Map(); + +const handleMessage = (event) => { + const { action, requestId, result } = event.data; + + // Check if this is the expected response action and if we have a stored resolver + if ( + action === 'QORTAL_REQUEST_PERMISSION_RESPONSE' && + responseResolvers.has(requestId) + ) { + // Resolve the stored promise with the result + responseResolvers.get(requestId)(result || false); + responseResolvers.delete(requestId); // Clean up after resolving + } +}; + +window.addEventListener('message', handleMessage); + +async function getUserPermission(payload, isFromExtension) { + return new Promise((resolve) => { + const requestId = `qortalRequest_${Date.now()}`; + responseResolvers.set(requestId, resolve); // Store resolver by requestId + const targetOrigin = window.location.origin; + + // Send the request message + window.postMessage( + { + action: 'QORTAL_REQUEST_PERMISSION', + payload, + requestId, + isFromExtension, + }, + targetOrigin + ); + + // Optional timeout to handle no response scenario + setTimeout(() => { + if (responseResolvers.has(requestId)) { + responseResolvers.get(requestId)(false); // Resolve with `false` if no response + responseResolvers.delete(requestId); + } + }, 60000); // 30-second timeout + }); +} + +export const getUserAccount = async ({ + isFromExtension, + appInfo, + skipAuth, +}) => { + try { + const value = + (await getPermission(`qAPPAutoAuth-${appInfo?.name}`)) || false; + let skip = false; + if (value) { + skip = true; + } + if (skipAuth) { + skip = true; + } + let resPermission; + if (!skip) { + resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.authenticate', { + postProcess: 'capitalizeFirstChar', + }), + checkbox1: { + value: false, + label: i18n.t('question:always_authenticate', { + postProcess: 'capitalizeFirstChar', + }), + }, + }, + isFromExtension + ); + } + + const { accepted = false, checkbox1 = false } = resPermission || {}; + if (resPermission) { + setPermission(`qAPPAutoAuth-${appInfo?.name}`, checkbox1); + } + if (accepted || skip) { + const wallet = await getSaveWallet(); + const address = wallet.address0; + const publicKey = wallet.publicKey; + return { + address, + publicKey, + }; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } catch (error) { + throw new Error( + i18n.t('auth:message.error.fetch_user_account', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const encryptData = async (data, sender) => { + let data64 = data.data64 || data.base64; + let publicKeys = data.publicKeys || []; + if (data?.file || data?.blob) { + data64 = await fileToBase64(data?.file || data?.blob); + } + if (!data64) { + throw new Error( + i18n.t('question:message.generic.include_data_encrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const privateKey = parsedData.privateKey; + const userPublicKey = parsedData.publicKey; + + const encryptDataResponse = encryptDataGroup({ + data64, + publicKeys: publicKeys, + privateKey, + userPublicKey, + }); + if (encryptDataResponse) { + return encryptDataResponse; + } else { + throw new Error( + i18n.t('question:message.error.encrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const encryptQortalGroupData = async (data, sender) => { + let data64 = data?.data64 || data?.base64; + let groupId = data?.groupId; + let isAdmins = data?.isAdmins; + if (!groupId) { + throw new Error( + i18n.t('question:message.generic.provide_group_id', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + if (data?.file || data?.blob) { + data64 = await fileToBase64(data?.file || data?.blob); + } + if (!data64) { + throw new Error( + i18n.t('question:message.generic.include_data_encrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + let secretKeyObject; + if (!isAdmins) { + if ( + groupSecretkeys[groupId] && + groupSecretkeys[groupId].secretKeyObject && + groupSecretkeys[groupId]?.timestamp && + Date.now() - groupSecretkeys[groupId]?.timestamp < 1200000 + ) { + secretKeyObject = groupSecretkeys[groupId].secretKeyObject; + } + + if (!secretKeyObject) { + const { names } = await getGroupAdmins(groupId); + + const publish = await getPublishesFromAdmins(names, groupId); + if (publish === false) + throw new Error( + i18n.t('question:message.error.no_group_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + const url = await createEndpoint( + `/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${ + publish.identifier + }?encoding=base64&rebuild=true` + ); + + const res = await fetch(url); + const resData = await res.text(); + + const decryptedKey: any = await decryptResource(resData, true); + + const dataint8Array = base64ToUint8Array(decryptedKey.data); + const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); + + if (!validateSecretKey(decryptedKeyToObject)) + throw new Error( + i18n.t('auth:message.error.invalid_secret_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + secretKeyObject = decryptedKeyToObject; + groupSecretkeys[groupId] = { + secretKeyObject, + timestamp: Date.now(), + }; + } + } else { + if ( + groupSecretkeys[`admins-${groupId}`] && + groupSecretkeys[`admins-${groupId}`].secretKeyObject && + groupSecretkeys[`admins-${groupId}`]?.timestamp && + Date.now() - groupSecretkeys[`admins-${groupId}`]?.timestamp < 1200000 + ) { + secretKeyObject = groupSecretkeys[`admins-${groupId}`].secretKeyObject; + } + + if (!secretKeyObject) { + const { names } = await getGroupAdmins(groupId); + + const publish = await getPublishesFromAdminsAdminSpace(names, groupId); + if (publish === false) + throw new Error( + i18n.t('question:message.error.no_group_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + const url = await createEndpoint( + `/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${ + publish.identifier + }?encoding=base64&rebuild=true` + ); + + const res = await fetch(url); + const resData = await res.text(); + const decryptedKey: any = await decryptResource(resData, true); + const dataint8Array = base64ToUint8Array(decryptedKey.data); + const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); + + if (!validateSecretKey(decryptedKeyToObject)) + throw new Error( + i18n.t('auth:message.error.invalid_secret_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + secretKeyObject = decryptedKeyToObject; + groupSecretkeys[`admins-${groupId}`] = { + secretKeyObject, + timestamp: Date.now(), + }; + } + } + + const resGroupEncryptedResource = encryptSingle({ + data64, + secretKeyObject: secretKeyObject, + }); + + if (resGroupEncryptedResource) { + return resGroupEncryptedResource; + } else { + throw new Error( + i18n.t('question:message.error.encrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const decryptQortalGroupData = async (data, sender) => { + let data64 = data?.data64 || data?.base64; + let groupId = data?.groupId; + let isAdmins = data?.isAdmins; + if (!groupId) { + throw new Error( + i18n.t('question:message.generic.provide_group_id', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + if (!data64) { + throw new Error( + i18n.t('question:message.generic.include_data_encrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + let secretKeyObject; + if (!isAdmins) { + if ( + groupSecretkeys[groupId] && + groupSecretkeys[groupId].secretKeyObject && + groupSecretkeys[groupId]?.timestamp && + Date.now() - groupSecretkeys[groupId]?.timestamp < 1200000 + ) { + secretKeyObject = groupSecretkeys[groupId].secretKeyObject; + } + if (!secretKeyObject) { + const { names } = await getGroupAdmins(groupId); + + const publish = await getPublishesFromAdmins(names, groupId); + if (publish === false) + throw new Error( + i18n.t('question:message.error.no_group_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + const url = await createEndpoint( + `/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${ + publish.identifier + }?encoding=base64&rebuild=true` + ); + + const res = await fetch(url); + const resData = await res.text(); + const decryptedKey: any = await decryptResource(resData, true); + + const dataint8Array = base64ToUint8Array(decryptedKey.data); + const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); + if (!validateSecretKey(decryptedKeyToObject)) + throw new Error( + i18n.t('auth:message.error.invalid_secret_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + secretKeyObject = decryptedKeyToObject; + groupSecretkeys[groupId] = { + secretKeyObject, + timestamp: Date.now(), + }; + } + } else { + if ( + groupSecretkeys[`admins-${groupId}`] && + groupSecretkeys[`admins-${groupId}`].secretKeyObject && + groupSecretkeys[`admins-${groupId}`]?.timestamp && + Date.now() - groupSecretkeys[`admins-${groupId}`]?.timestamp < 1200000 + ) { + secretKeyObject = groupSecretkeys[`admins-${groupId}`].secretKeyObject; + } + if (!secretKeyObject) { + const { names } = await getGroupAdmins(groupId); + + const publish = await getPublishesFromAdminsAdminSpace(names, groupId); + if (publish === false) + throw new Error( + i18n.t('question:message.error.no_group_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + const url = await createEndpoint( + `/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${ + publish.identifier + }?encoding=base64&rebuild=true` + ); + + const res = await fetch(url); + const resData = await res.text(); + const decryptedKey: any = await decryptResource(resData, true); + + const dataint8Array = base64ToUint8Array(decryptedKey.data); + const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); + if (!validateSecretKey(decryptedKeyToObject)) + throw new Error( + i18n.t('auth:message.error.invalid_secret_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + secretKeyObject = decryptedKeyToObject; + groupSecretkeys[`admins-${groupId}`] = { + secretKeyObject, + timestamp: Date.now(), + }; + } + } + + const resGroupDecryptResource = decryptSingle({ + data64, + secretKeyObject: secretKeyObject, + skipDecodeBase64: true, + }); + if (resGroupDecryptResource) { + return resGroupDecryptResource; + } else { + throw new Error( + i18n.t('question:message.error.encrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const encryptDataWithSharingKey = async (data, sender) => { + let data64 = data?.data64 || data?.base64; + let publicKeys = data.publicKeys || []; + if (data?.file || data?.blob) { + data64 = await fileToBase64(data?.file || data?.blob); + } + if (!data64) { + throw new Error( + i18n.t('question:message.generic.include_data_encrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const symmetricKey = createSymmetricKeyAndNonce(); + const dataObject = { + data: data64, + key: symmetricKey.messageKey, + }; + const dataObjectBase64 = await objectToBase64(dataObject); + + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const privateKey = parsedData.privateKey; + const userPublicKey = parsedData.publicKey; + + const encryptDataResponse = encryptDataGroup({ + data64: dataObjectBase64, + publicKeys: publicKeys, + privateKey, + userPublicKey, + customSymmetricKey: symmetricKey.messageKey, + }); + if (encryptDataResponse) { + return encryptDataResponse; + } else { + throw new Error( + i18n.t('question:message.error.encrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const decryptDataWithSharingKey = async (data, sender) => { + const { encryptedData, key } = data; + + if (!encryptedData) { + throw new Error( + i18n.t('question:message.generic.include_data_decrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + const decryptedData = await decryptGroupEncryptionWithSharingKey({ + data64EncryptedData: encryptedData, + key, + }); + const base64ToObject = JSON.parse(atob(decryptedData)); + + if (!base64ToObject.data) + throw new Error( + i18n.t('question:message.error.no_data_encrypted_resource', { + postProcess: 'capitalizeFirstChar', + }) + ); + return base64ToObject.data; +}; + +export const getHostedData = async (data, isFromExtension) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const resPermission = await getUserPermission( + { + text1: i18n.t('question:message.error.submit_sell_order', { + postProcess: 'capitalizeFirstChar', + }), + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const limit = data?.limit ? data?.limit : 20; + const query = data?.query ? data?.query : ''; + const offset = data?.offset ? data?.offset : 0; + + let urlPath = `/arbitrary/hosted/resources/?limit=${limit}&offset=${offset}`; + if (query) { + urlPath = urlPath + `&query=${query}`; + } + + const url = await createEndpoint(urlPath); + const response = await fetch(url); + const dataResponse = await response.json(); + return dataResponse; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_list', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const deleteHostedData = async (data, isFromExtension) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const requiredFields = ['hostedData']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.delete_hosts_resources', { + size: data?.hostedData?.length, + postProcess: 'capitalizeFirstChar', + }), + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const { hostedData } = data; + + for (const hostedDataItem of hostedData) { + try { + const url = await createEndpoint( + `/arbitrary/resource/${hostedDataItem.service}/${hostedDataItem.name}/${hostedDataItem.identifier}` + ); + await fetch(url, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (error) { + console.log(error); + } + } + + return true; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_delete_hosted_resources', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; +export const decryptData = async (data) => { + const { encryptedData, publicKey } = data; + + if (!encryptedData) { + throw new Error(`Missing fields: encryptedData`); + } + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8Array = base64ToUint8Array(encryptedData); + const startsWithQortalEncryptedData = uint8ArrayStartsWith( + uint8Array, + 'qortalEncryptedData' + ); + if (startsWithQortalEncryptedData) { + if (!publicKey) { + throw new Error(`Missing fields: publicKey`); + } + + const decryptedDataToBase64 = decryptDeprecatedSingle( + uint8Array, + publicKey, + uint8PrivateKey + ); + return decryptedDataToBase64; + } + const startsWithQortalGroupEncryptedData = uint8ArrayStartsWith( + uint8Array, + 'qortalGroupEncryptedData' + ); + if (startsWithQortalGroupEncryptedData) { + const decryptedData = decryptGroupDataQortalRequest( + encryptedData, + parsedData.privateKey + ); + const decryptedDataToBase64 = uint8ArrayToBase64(decryptedData); + return decryptedDataToBase64; + } + throw new Error( + i18n.t('question:message.error.encrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); +}; + +export const getListItems = async (data, isFromExtension) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const requiredFields = ['list_name']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const value = (await getPermission('qAPPAutoLists')) || false; + + let skip = false; + if (value) { + skip = true; + } + let resPermission; + let acceptedVar; + let checkbox1Var; + if (!skip) { + resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.access_list', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: data.list_name, + checkbox1: { + value: value, + label: i18n.t('question:always_retrieve_list', { + postProcess: 'capitalizeFirstChar', + }), + }, + }, + isFromExtension + ); + const { accepted, checkbox1 } = resPermission; + acceptedVar = accepted; + checkbox1Var = checkbox1; + setPermission('qAPPAutoLists', checkbox1); + } + + if (acceptedVar || skip) { + const url = await createEndpoint(`/lists/${data.list_name}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_list', { + postProcess: 'capitalizeFirstChar', + }) + ); + + const list = await response.json(); + return list; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_share_list', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const addListItems = async (data, isFromExtension) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const requiredFields = ['list_name', 'items']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const items = data.items; + const list_name = data.list_name; + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.all_item_list', { + name: list_name, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: items.join(', '), + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const url = await createEndpoint(`/lists/${list_name}`); + const body = { + items: items, + }; + const bodyToString = JSON.stringify(body); + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: bodyToString, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.add_to_list', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_add_list', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const deleteListItems = async (data, isFromExtension) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const requiredFields = ['list_name']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + if (!data?.item && !data?.items) { + throw new Error( + i18n.t('question:message.error.missing_fields', { + fields: 'items', + postProcess: 'capitalizeFirstChar', + }) + ); + } + const item = data?.item; + const items = data?.items; + const list_name = data.list_name; + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.remove_from_list', { + name: list_name, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: items ? JSON.stringify(items) : item, + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const url = await createEndpoint(`/lists/${list_name}`); + const body = { + items: items || [item], + }; + const bodyToString = JSON.stringify(body); + const response = await fetch(url, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + body: bodyToString, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.add_to_list', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_delete_from_list', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const publishQDNResource = async ( + data: any, + sender, + isFromExtension +) => { + const requiredFields = ['service']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + if (!data.file && !data.data64 && !data.base64) { + throw new Error( + i18n.t('question:message.error.no_data_file_submitted', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + // Use "default" if user hasn't specified an identifier + const service = data.service; + const appFee = data?.appFee ? +data.appFee : undefined; + const appFeeRecipient = data?.appFeeRecipient; + let hasAppFee = false; + if (appFee && appFee > 0 && appFeeRecipient) { + hasAppFee = true; + } + const registeredName = await getNameInfo(); + const name = registeredName; + if (!name) { + throw new Error( + i18n.t('question:message.error.user_qortal_name', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + let identifier = data.identifier; + let data64 = data.data64 || data.base64; + const filename = data.filename; + const title = data.title; + const description = data.description; + const category = data.category; + + const tags = data?.tags || []; + const result = {}; + + // Fill tags dynamically while maintaining backward compatibility + for (let i = 0; i < 5; i++) { + result[`tag${i + 1}`] = tags[i] || data[`tag${i + 1}`] || undefined; + } + + // Access tag1 to tag5 from result + const { tag1, tag2, tag3, tag4, tag5 } = result; + + if (data.identifier == null) { + identifier = 'default'; + } + if (data?.file || data?.blob) { + data64 = await fileToBase64(data?.file || data?.blob); + } + if ( + data.encrypt && + (!data.publicKeys || + (Array.isArray(data.publicKeys) && data.publicKeys.length === 0)) + ) { + throw new Error( + i18n.t('question:message.error.encryption_requires_public_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + if (data.encrypt) { + try { + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const privateKey = parsedData.privateKey; + const userPublicKey = parsedData.publicKey; + const encryptDataResponse = encryptDataGroup({ + data64, + publicKeys: data.publicKeys, + privateKey, + userPublicKey, + }); + if (encryptDataResponse) { + data64 = encryptDataResponse; + } + } catch (error) { + throw new Error( + error.message || + i18n.t('question:message.error.upload_encryption', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } + + const fee = await getFee('ARBITRARY'); + + const handleDynamicValues = {}; + if (hasAppFee) { + const feePayment = await getFee('PAYMENT'); + + (handleDynamicValues['appFee'] = +appFee + +feePayment.fee), + (handleDynamicValues['checkbox1'] = { + value: true, + label: i18n.t('question:accept_app_fee', { + postProcess: 'capitalizeFirstChar', + }), + }); + } + if (!!data?.encrypt) { + handleDynamicValues['highlightedText'] = `isEncrypted: ${!!data.encrypt}`; + } + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.publish_qdn', { + postProcess: 'capitalizeFirstChar', + }), + text2: `service: ${service}`, + text3: `identifier: ${identifier || null}`, + fee: fee.fee, + ...handleDynamicValues, + }, + isFromExtension + ); + const { accepted, checkbox1 = false } = resPermission; + if (accepted) { + try { + const resPublish = await publishData({ + registeredName: encodeURIComponent(name), + file: data64, + service: service, + identifier: encodeURIComponent(identifier), + uploadType: 'file', + isBase64: true, + filename: filename, + title, + description, + category, + tag1, + tag2, + tag3, + tag4, + tag5, + apiVersion: 2, + withFee: true, + }); + if (resPublish?.signature && hasAppFee && checkbox1) { + sendCoinFunc( + { + amount: appFee, + receiver: appFeeRecipient, + }, + true + ); + } + return resPublish; + } catch (error) { + throw new Error(error?.message || 'Upload failed'); + } + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const checkArrrSyncStatus = async (seed) => { + const _url = await createEndpoint(`/crosschain/arrr/syncstatus`); + let tries = 0; // Track the number of attempts + + while (tries < 36) { + const response = await fetch(_url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: seed, + }); + + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + + if (res.indexOf('<') > -1 || res !== 'Synchronized') { + // Wait 2 seconds before trying again + await new Promise((resolve) => setTimeout(resolve, 2000)); + tries += 1; + } else { + // If the response doesn't meet the two conditions, exit the function + return; + } + } + + // If we exceed N tries, throw an error + throw new Error( + i18n.t('question:message.error.synchronization_attempts', { + quantity: 36, + postProcess: 'capitalizeFirstChar', + }) + ); +}; + +export const publishMultipleQDNResources = async ( + data: any, + sender, + isFromExtension +) => { + const requiredFields = ['resources']; + const missingFields: string[] = []; + + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const resources = data.resources; + if (!Array.isArray(resources)) { + throw new Error( + i18n.t('group:message.generic.invalid_data', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + if (resources.length === 0) { + throw new Error( + i18n.t('question:message.error.no_resources_publish', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + const encrypt = data?.encrypt; + + for (const resource of resources) { + const resourceEncrypt = encrypt && resource?.disableEncrypt !== true; + + if (!resourceEncrypt && resource?.service.endsWith('_PRIVATE')) { + const errorMsg = i18n.t('question:message.error.only_encrypted_data', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } else if (resourceEncrypt && !resource?.service.endsWith('_PRIVATE')) { + const errorMsg = i18n.t('question:message.error.use_private_service', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + } + + const fee = await getFee('ARBITRARY'); + const registeredName = await getNameInfo(); + const name = registeredName; + + if (!name) { + throw new Error( + i18n.t('question:message.error.registered_name', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + const appFee = data?.appFee ? +data.appFee : undefined; + const appFeeRecipient = data?.appFeeRecipient; + let hasAppFee = false; + + if (appFee && appFee > 0 && appFeeRecipient) { + hasAppFee = true; + } + + const handleDynamicValues = {}; + if (hasAppFee) { + const feePayment = await getFee('PAYMENT'); + + (handleDynamicValues['appFee'] = +appFee + +feePayment.fee), + (handleDynamicValues['checkbox1'] = { + value: true, + label: i18n.t('question:accept_app_fee', { + postProcess: 'capitalizeFirstChar', + }), + }); + } + if (data?.encrypt) { + handleDynamicValues['highlightedText'] = `isEncrypted: ${!!data.encrypt}`; + } + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.publish_qdn', { + postProcess: 'capitalizeFirstChar', + }), + html: ` +

+ + + ${data.resources + .map( + (resource) => ` +
+
Service: ${ + resource.service + }
+
Name: ${name}
+
Identifier: ${ + resource.identifier + }
+ ${ + resource.filename + ? `
Filename: ${resource.filename}
` + : '' + } +
` + ) + .join('')} +
+ + `, + fee: +fee.fee * resources.length, + ...handleDynamicValues, + }, + isFromExtension + ); + + const { accepted, checkbox1 = false } = resPermission; + if (!accepted) { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + type FailedPublish = { + reason: string; + identifier: any; + service: any; + }; + + const failedPublishesIdentifiers: FailedPublish[] = []; + + for (const resource of resources) { + try { + const requiredFields = ['service']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!resource[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + failedPublishesIdentifiers.push({ + reason: errorMsg, + identifier: resource.identifier, + service: resource.service, + }); + continue; + } + if (!resource.file && !resource.data64 && !resource?.base64) { + const errorMsg = i18n.t( + 'question:message.error.no_data_file_submitted', + { + postProcess: 'capitalizeFirstChar', + } + ); + failedPublishesIdentifiers.push({ + reason: errorMsg, + identifier: resource.identifier, + service: resource.service, + }); + continue; + } + const service = resource.service; + let identifier = resource.identifier; + let data64 = resource?.data64 || resource?.base64; + const filename = resource.filename; + const title = resource.title; + const description = resource.description; + const category = resource.category; + const tags = resource?.tags || []; + const result = {}; + + // Fill tags dynamically while maintaining backward compatibility + for (let i = 0; i < 5; i++) { + result[`tag${i + 1}`] = tags[i] || resource[`tag${i + 1}`] || undefined; + } + + // Access tag1 to tag5 from result + const { tag1, tag2, tag3, tag4, tag5 } = result; + const resourceEncrypt = encrypt && resource?.disableEncrypt !== true; + if (resource.identifier == null) { + identifier = 'default'; + } + if (!resourceEncrypt && service.endsWith('_PRIVATE')) { + const errorMsg = i18n.t('question:message.error.only_encrypted_data', { + postProcess: 'capitalizeFirstChar', + }); + failedPublishesIdentifiers.push({ + reason: errorMsg, + identifier: resource.identifier, + service: resource.service, + }); + continue; + } + if (resource.file) { + data64 = await fileToBase64(resource.file); + } + if (resourceEncrypt) { + try { + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const privateKey = parsedData.privateKey; + const userPublicKey = parsedData.publicKey; + const encryptDataResponse = encryptDataGroup({ + data64, + publicKeys: data.publicKeys, + privateKey, + userPublicKey, + }); + if (encryptDataResponse) { + data64 = encryptDataResponse; + } + } catch (error) { + const errorMsg = + error?.message || + i18n.t('question:message.error.upload_encryption', { + postProcess: 'capitalizeFirstChar', + }); + failedPublishesIdentifiers.push({ + reason: errorMsg, + identifier: resource.identifier, + service: resource.service, + }); + continue; + } + } + + try { + await retryTransaction( + publishData, + [ + { + registeredName: encodeURIComponent(name), + file: data64, + service: service, + identifier: encodeURIComponent(identifier), + uploadType: 'file', + isBase64: true, + filename: filename, + title, + description, + category, + tag1, + tag2, + tag3, + tag4, + tag5, + apiVersion: 2, + withFee: true, + }, + ], + true + ); + await new Promise((res) => { + setTimeout(() => { + res(); + }, 1000); + }); + } catch (error) { + const errorMsg = + error.message || + i18n.t('question:message.error.upload', { + postProcess: 'capitalizeFirstChar', + }); + failedPublishesIdentifiers.push({ + reason: errorMsg, + identifier: resource.identifier, + service: resource.service, + }); + } + } catch (error) { + failedPublishesIdentifiers.push({ + reason: + error?.message || + i18n.t('question:message.error.unknown_error', { + postProcess: 'capitalizeFirstChar', + }), + identifier: resource.identifier, + service: resource.service, + }); + } + } + if (failedPublishesIdentifiers.length > 0) { + const obj = { + message: i18n.t('question:message.error.resources_publish', { + postProcess: 'capitalizeFirstChar', + }), + }; + obj['error'] = { + unsuccessfulPublishes: failedPublishesIdentifiers, + }; + return obj; + } + if (hasAppFee && checkbox1) { + sendCoinFunc( + { + amount: appFee, + receiver: appFeeRecipient, + }, + true + ); + } + return true; +}; + +export const voteOnPoll = async (data, isFromExtension) => { + const requiredFields = ['pollName', 'optionIndex']; + const missingFields: string[] = []; + + requiredFields.forEach((field) => { + if (!data[field] && data[field] !== 0) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const pollName = data.pollName; + const optionIndex = data.optionIndex; + let pollInfo = null; + try { + const url = await createEndpoint(`/polls/${encodeURIComponent(pollName)}`); + const response = await fetch(url); + if (!response.ok) { + const errorMessage = await parseErrorResponse( + response, + i18n.t('question:message.error.fetch_poll', { + postProcess: 'capitalizeFirstChar', + }) + ); + throw new Error(errorMessage); + } + + pollInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_poll', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + if (!pollInfo || pollInfo.error) { + const errorMsg = + (pollInfo && pollInfo.message) || + i18n.t('question:message.error.no_poll', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + try { + const optionName = pollInfo.pollOptions[optionIndex].optionName; + const resVoteOnPoll = await _voteOnPoll( + { pollName, optionIndex, optionName }, + isFromExtension + ); + return resVoteOnPoll; + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.poll_vote', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const createPoll = async (data, isFromExtension) => { + const requiredFields = [ + 'pollName', + 'pollDescription', + 'pollOptions', + 'pollOwnerAddress', + ]; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const pollName = data.pollName; + const pollDescription = data.pollDescription; + const pollOptions = data.pollOptions; + try { + const resCreatePoll = await _createPoll( + { + pollName, + pollDescription, + options: pollOptions, + }, + isFromExtension + ); + return resCreatePoll; + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.poll_create', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +function isBase64(str) { + const base64Regex = + /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/; + return base64Regex.test(str) && str.length % 4 === 0; +} + +function checkValue(value) { + if (typeof value === 'string') { + if (isBase64(value)) { + return 'string'; + } else { + return 'string'; + } + } else if (typeof value === 'object' && value !== null) { + return 'object'; + } else { + throw new Error( + i18n.t('question:message.error.invalid_fullcontent', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +} + +export const sendChatMessage = async (data, isFromExtension, appInfo) => { + const message = data?.message; + const fullMessageObject = data?.fullMessageObject || data?.fullContent; + const recipient = data?.destinationAddress || data.recipient; + const groupId = data.groupId; + const isRecipient = groupId === undefined; + const chatReference = data?.chatReference; + if (groupId === undefined && recipient === undefined) { + throw new Error( + i18n.t('question:provide_recipient_group_id', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + let fullMessageObjectType; + if (fullMessageObject) { + fullMessageObjectType = checkValue(fullMessageObject); + } + const value = + (await getPermission(`qAPPSendChatMessage-${appInfo?.name}`)) || false; + let skip = false; + if (value) { + skip = true; + } + let resPermission; + if (!skip) { + resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.send_chat_message', { + postProcess: 'capitalizeFirstChar', + }), + text2: isRecipient + ? i18n.t('question:to_recipient', { + recipient: recipient, + postProcess: 'capitalizeFirstChar', + }) + : i18n.t('question:to_group', { + group_id: groupId, + postProcess: 'capitalizeFirstChar', + }), + text3: fullMessageObject + ? fullMessageObjectType === 'string' + ? `${fullMessageObject?.slice(0, 25)}${fullMessageObject?.length > 25 ? '...' : ''}` + : `${JSON.stringify(fullMessageObject)?.slice(0, 25)}${JSON.stringify(fullMessageObject)?.length > 25 ? '...' : ''}` + : `${message?.slice(0, 25)}${message?.length > 25 ? '...' : ''}`, + checkbox1: { + value: false, + label: i18n.t('question:always_chat_messages', { + postProcess: 'capitalizeFirstChar', + }), + }, + }, + isFromExtension + ); + } + const { accepted = false, checkbox1 = false } = resPermission || {}; + if (resPermission && accepted) { + setPermission(`qAPPSendChatMessage-${appInfo?.name}`, checkbox1); + } + if (accepted || skip) { + const tiptapJson = { + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: message, + }, + ], + }, + ], + }; + const messageObject = fullMessageObject + ? fullMessageObject + : { + messageText: tiptapJson, + images: [], + repliedTo: '', + version: 3, + }; + + let stringifyMessageObject = JSON.stringify(messageObject); + if (fullMessageObjectType === 'string') { + stringifyMessageObject = messageObject; + } + + const balance = await getBalanceInfo(); + const hasEnoughBalance = +balance < 4 ? false : true; + if (!hasEnoughBalance) { + throw new Error( + i18n.t('group:message.error.qortals_required', { + quantity: 4, + postProcess: 'capitalizeFirstChar', + }) + ); + } + if (isRecipient && recipient) { + const url = await createEndpoint(`/addresses/publickey/${recipient}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_recipient_public_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + + let key; + let hasPublicKey; + let res; + const contentType = response.headers.get('content-type'); + + // If the response is JSON, parse it as JSON + if (contentType && contentType.includes('application/json')) { + res = await response.json(); + } else { + // Otherwise, treat it as plain text + res = await response.text(); + } + if (res?.error === 102) { + key = ''; + hasPublicKey = false; + } else if (res !== false) { + key = res; + hasPublicKey = true; + } else { + key = ''; + hasPublicKey = false; + } + + if (!hasPublicKey && isRecipient) { + throw new Error( + 'Cannot send an encrypted message to this user since they do not have their publickey on chain.' + ); + } + let _reference = new Uint8Array(64); + self.crypto.getRandomValues(_reference); + + let sendTimestamp = Date.now(); + + let reference = Base58.encode(_reference); + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey, + }; + + let handleDynamicValues = {}; + if (chatReference) { + handleDynamicValues['chatReference'] = chatReference; + } + + const tx = await createTransaction(18, keyPair, { + timestamp: sendTimestamp, + recipient: recipient, + recipientPublicKey: key, + hasChatReference: chatReference ? 1 : 0, + message: stringifyMessageObject, + lastReference: reference, + proofOfWorkNonce: 0, + isEncrypted: 1, + isText: 1, + ...handleDynamicValues, + }); + + const chatBytes = tx.chatBytes; + const difficulty = 8; + const { nonce, chatBytesArray } = await performPowTask( + chatBytes, + difficulty + ); + + let _response = await signChatFunc(chatBytesArray, nonce, null, keyPair); + if (_response?.error) { + throw new Error(_response?.message); + } + return _response; + } else if (!isRecipient && groupId) { + let _reference = new Uint8Array(64); + self.crypto.getRandomValues(_reference); + + let reference = Base58.encode(_reference); + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey, + }; + + let handleDynamicValues = {}; + if (chatReference) { + handleDynamicValues['chatReference'] = chatReference; + } + + const txBody = { + timestamp: Date.now(), + groupID: Number(groupId), + hasReceipient: 0, + hasChatReference: chatReference ? 1 : 0, + message: stringifyMessageObject, + lastReference: reference, + proofOfWorkNonce: 0, + isEncrypted: 0, // Set default to not encrypted for groups + isText: 1, + ...handleDynamicValues, + }; + + const tx = await createTransaction(181, keyPair, txBody); + + // if (!hasEnoughBalance) { + // throw new Error("Must have at least 4 QORT to send a chat message"); + // } + + const chatBytes = tx.chatBytes; + const difficulty = 8; + const { nonce, chatBytesArray } = await performPowTask( + chatBytes, + difficulty + ); + + let _response = await signChatFunc(chatBytesArray, nonce, null, keyPair); + if (_response?.error) { + throw new Error(_response?.message); + } + return _response; + } else { + throw new Error( + i18n.t('question:provide_recipient_group_id', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_send_message', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const joinGroup = async (data, isFromExtension) => { + const requiredFields = ['groupId']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${data.groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const fee = await getFee('JOIN_GROUP'); + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:message.generic.confirm_join_group', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `${groupInfo.groupName}`, + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const groupId = data.groupId; + + if (!groupInfo || groupInfo.error) { + const errorMsg = + (groupInfo && groupInfo.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + try { + const resJoinGroup = await joinGroupFunc({ groupId }); + return resJoinGroup; + } catch (error) { + throw new Error( + error?.message || + i18n.t('group:message.error.group_join', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_join', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const saveFile = async (data, sender, isFromExtension, snackMethods) => { + try { + const requiredFields = ['filename', 'blob']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const filename = data.filename; + const blob = data.blob; + const resPermission = await getUserPermission( + { + text1: i18n.t('question:download_file', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `${filename}`, + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const mimeType = blob.type || data.mimeType; + let backupExention = filename.split('.').pop(); + if (backupExention) { + backupExention = '.' + backupExention; + } + const fileExtension = mimeToExtensionMap[mimeType] || backupExention; + let fileHandleOptions = {}; + if (!mimeType) { + throw new Error( + i18n.t('question:message.error.mime_type', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + if (!fileExtension) { + throw new Error( + i18n.t('question:message.error.file_extension', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + if (fileExtension && mimeType) { + fileHandleOptions = { + accept: { + [mimeType]: [fileExtension], + }, + }; + } + + showSaveFilePicker( + { + filename, + mimeType, + blob, + }, + snackMethods + ); + return true; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_save_file', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } catch (error) { + throw new Error( + error?.message || + i18n.t('core:message.error.initiate_download', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const deployAt = async (data, isFromExtension) => { + const requiredFields = [ + 'name', + 'description', + 'tags', + 'creationBytes', + 'amount', + 'assetId', + 'type', + ]; + + const missingFields: string[] = []; + + requiredFields.forEach((field) => { + if (!data[field] && data[field] !== 0) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + try { + const resDeployAt = await _deployAt( + { + name: data.name, + description: data.description, + tags: data.tags, + creationBytes: data.creationBytes, + amount: data.amount, + assetId: data.assetId, + atType: data.type, + }, + isFromExtension + ); + return resDeployAt; + } catch (error) { + throw new Error( + error?.message || + i18n.t('group:message.error.group_join', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const getUserWallet = async (data, isFromExtension, appInfo) => { + const requiredFields = ['coin']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const isGateway = await isRunningGateway(); + + if (data?.coin === 'ARRR' && isGateway) + throw new Error( + i18n.t('question:message.error.gateway_wallet_local_node', { + token: 'ARRR', + postProcess: 'capitalizeFirstChar', + }) + ); + + const value = + (await getPermission( + `qAPPAutoGetUserWallet-${appInfo?.name}-${data.coin}` + )) || false; + let skip = false; + if (value) { + skip = true; + } + + let resPermission; + + if (!skip) { + resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.get_wallet_info', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `coin: ${data.coin}`, + checkbox1: { + value: true, + label: i18n.t('question:always_retrieve_wallet', { + postProcess: 'capitalizeFirstChar', + }), + }, + }, + isFromExtension + ); + } + const { accepted = false, checkbox1 = false } = resPermission || {}; + + if (resPermission) { + setPermission( + `qAPPAutoGetUserWallet-${appInfo?.name}-${data.coin}`, + checkbox1 + ); + } + + if (accepted || skip) { + let coin = data.coin; + let userWallet = {}; + let arrrAddress = ''; + const wallet = await getSaveWallet(); + const address = wallet.address0; + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const arrrSeed58 = parsedData.arrrSeed58; + if (coin === 'ARRR') { + const bodyToString = arrrSeed58; + const url = await createEndpoint(`/crosschain/arrr/walletaddress`); + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: bodyToString, + }); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + if (res?.error && res?.message) { + throw new Error(res.message); + } + arrrAddress = res; + } + switch (coin) { + case 'QORT': + userWallet['address'] = address; + userWallet['publickey'] = parsedData.publicKey; + break; + case 'BTC': + userWallet['address'] = parsedData.btcAddress; + userWallet['publickey'] = parsedData.btcPublicKey; + break; + case 'LTC': + userWallet['address'] = parsedData.ltcAddress; + userWallet['publickey'] = parsedData.ltcPublicKey; + break; + case 'DOGE': + userWallet['address'] = parsedData.dogeAddress; + userWallet['publickey'] = parsedData.dogePublicKey; + break; + case 'DGB': + userWallet['address'] = parsedData.dgbAddress; + userWallet['publickey'] = parsedData.dgbPublicKey; + break; + case 'RVN': + userWallet['address'] = parsedData.rvnAddress; + userWallet['publickey'] = parsedData.rvnPublicKey; + break; + case 'ARRR': + await checkArrrSyncStatus(parsedData.arrrSeed58); + userWallet['address'] = arrrAddress; + break; + default: + break; + } + return userWallet; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const getWalletBalance = async ( + data, + bypassPermission?: boolean, + isFromExtension?: boolean, + appInfo?: any +) => { + const requiredFields = ['coin']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const isGateway = await isRunningGateway(); + + if (data?.coin === 'ARRR' && isGateway) + throw new Error( + i18n.t('question:message.error.gateway_balance_local_node', { + token: 'ARRR', + postProcess: 'capitalizeFirstChar', + }) + ); + + const value = + (await getPermission( + `qAPPAutoWalletBalance-${appInfo?.name}-${data.coin}` + )) || false; + let skip = false; + if (value) { + skip = true; + } + let resPermission; + + if (!bypassPermission && !skip) { + resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.fetch_balance', { + coin: data.coin, // TODO highlight coin in the modal + postProcess: 'capitalizeFirstChar', + }), + checkbox1: { + value: true, + label: i18n.t('question:always_retrieve_balance', { + postProcess: 'capitalizeFirstChar', + }), + }, + }, + isFromExtension + ); + } + const { accepted = false, checkbox1 = false } = resPermission || {}; + if (resPermission) { + setPermission( + `qAPPAutoWalletBalance-${appInfo?.name}-${data.coin}`, + checkbox1 + ); + } + if (accepted || bypassPermission || skip) { + let coin = data.coin; + const wallet = await getSaveWallet(); + const address = wallet.address0; + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + if (coin === 'QORT') { + let qortAddress = address; + try { + const url = await createEndpoint(`/addresses/balance/${qortAddress}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_balance', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.fetch_wallet', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else { + let _url = ``; + let _body = null; + switch (coin) { + case 'BTC': + _url = await createEndpoint(`/crosschain/btc/walletbalance`); + + _body = parsedData.btcPublicKey; + break; + case 'LTC': + _url = await createEndpoint(`/crosschain/ltc/walletbalance`); + _body = parsedData.ltcPublicKey; + break; + case 'DOGE': + _url = await createEndpoint(`/crosschain/doge/walletbalance`); + _body = parsedData.dogePublicKey; + break; + case 'DGB': + _url = await createEndpoint(`/crosschain/dgb/walletbalance`); + _body = parsedData.dgbPublicKey; + break; + case 'RVN': + _url = await createEndpoint(`/crosschain/rvn/walletbalance`); + _body = parsedData.rvnPublicKey; + break; + case 'ARRR': + await checkArrrSyncStatus(parsedData.arrrSeed58); + _url = await createEndpoint(`/crosschain/arrr/walletbalance`); + _body = parsedData.arrrSeed58; + break; + default: + break; + } + try { + const response = await fetch(_url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: _body, + }); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + if (res?.error && res?.message) { + throw new Error(res.message); + } + if (isNaN(Number(res))) { + throw new Error( + i18n.t('question:message.error.fetch_balance', { + postProcess: 'capitalizeFirstChar', + }) + ); + } else { + return (Number(res) / 1e8).toFixed(8); + } + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.fetch_balance', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +const getPirateWallet = async (arrrSeed58) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.error.gateway_retrieve_balance', { + token: 'PIRATECHAIN', + postProcess: 'capitalizeFirstChar', + }) + ); + } + const bodyToString = arrrSeed58; + await checkArrrSyncStatus(bodyToString); + const url = await createEndpoint(`/crosschain/arrr/walletaddress`); + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: bodyToString, + }); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + if (res?.error && res?.message) { + throw new Error(res.message); + } + return res; +}; + +export const getUserWalletFunc = async (coin) => { + let userWallet = {}; + const wallet = await getSaveWallet(); + const address = wallet.address0; + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + switch (coin) { + case 'QORT': + userWallet['address'] = address; + userWallet['publickey'] = parsedData.publicKey; + break; + case 'BTC': + case 'BITCOIN': + userWallet['address'] = parsedData.btcAddress; + userWallet['publickey'] = parsedData.btcPublicKey; + break; + case 'LTC': + case 'LITECOIN': + userWallet['address'] = parsedData.ltcAddress; + userWallet['publickey'] = parsedData.ltcPublicKey; + break; + case 'DOGE': + case 'DOGECOIN': + userWallet['address'] = parsedData.dogeAddress; + userWallet['publickey'] = parsedData.dogePublicKey; + break; + case 'DGB': + case 'DIGIBYTE': + userWallet['address'] = parsedData.dgbAddress; + userWallet['publickey'] = parsedData.dgbPublicKey; + break; + case 'RVN': + case 'RAVENCOIN': + userWallet['address'] = parsedData.rvnAddress; + userWallet['publickey'] = parsedData.rvnPublicKey; + break; + case 'ARRR': + case 'PIRATECHAIN': + const arrrAddress = await getPirateWallet(parsedData.arrrSeed58); + userWallet['address'] = arrrAddress; + break; + default: + break; + } + return userWallet; +}; + +export const getUserWalletInfo = async (data, isFromExtension, appInfo) => { + const requiredFields = ['coin']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + if (data?.coin === 'ARRR') { + throw new Error( + i18n.t('question:message.error.token_not_supported', { + token: 'ARRR', + postProcess: 'capitalizeFirstChar', + }) + ); + } + const value = + (await getPermission(`getUserWalletInfo-${appInfo?.name}-${data.coin}`)) || + false; + let skip = false; + if (value) { + skip = true; + } + let resPermission; + + if (!skip) { + resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.get_wallet_info', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `coin: ${data.coin}`, + checkbox1: { + value: true, + label: i18n.t('question:always_retrieve_wallet', { + postProcess: 'capitalizeFirstChar', + }), + }, + }, + isFromExtension + ); + } + const { accepted = false, checkbox1 = false } = resPermission || {}; + + if (resPermission) { + setPermission(`getUserWalletInfo-${appInfo?.name}-${data.coin}`, checkbox1); + } + + if (accepted || skip) { + let coin = data.coin; + let walletKeys = await getUserWalletFunc(coin); + + const _url = await createEndpoint( + `/crosschain/` + data.coin.toLowerCase() + `/addressinfos` + ); + let _body = { xpub58: walletKeys['publickey'] }; + try { + const response = await fetch(_url, { + method: 'POST', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(_body), + }); + if (!response?.ok) + throw new Error( + i18n.t('question:message.error.fetch_wallet_info', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + if (res?.error && res?.message) { + throw new Error(res.message); + } + + return res; + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.fetch_wallet', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const getUserWalletTransactions = async ( + data, + isFromExtension, + appInfo +) => { + const requiredFields = ['coin']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const value = + (await getPermission( + `getUserWalletTransactions-${appInfo?.name}-${data.coin}` + )) || false; + let skip = false; + if (value) { + skip = true; + } + let resPermission; + + if (!skip) { + resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.get_wallet_transactions', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `coin: ${data.coin}`, + checkbox1: { + value: true, + label: i18n.t('question:always_retrieve_wallet_transactions', { + postProcess: 'capitalizeFirstChar', + }), + }, + }, + isFromExtension + ); + } + const { accepted = false, checkbox1 = false } = resPermission || {}; + + if (resPermission) { + setPermission( + `getUserWalletTransactions-${appInfo?.name}-${data.coin}`, + checkbox1 + ); + } + + if (accepted || skip) { + const coin = data.coin; + const walletKeys = await getUserWalletFunc(coin); + let publicKey; + if (data?.coin === 'ARRR') { + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + publicKey = parsedData.arrrSeed58; + } else { + publicKey = walletKeys['publickey']; + } + + const _url = await createEndpoint( + `/crosschain/` + data.coin.toLowerCase() + `/wallettransactions` + ); + const _body = publicKey; + try { + const response = await fetch(_url, { + method: 'POST', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + body: _body, + }); + if (!response?.ok) + throw new Error( + i18n.t('question:message.error.fetch_wallet_transactions', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + if (res?.error && res?.message) { + throw new Error(res.message); + } + + return res; + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.fetch_wallet_transactions', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const getCrossChainServerInfo = async (data) => { + const requiredFields = ['coin']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const _url = `/crosschain/` + data.coin.toLowerCase() + `/serverinfos`; + try { + const url = await createEndpoint(_url); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_generic', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + if (res?.error && res?.message) { + throw new Error(res.message); + } + return res.servers; + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.server_info', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const getTxActivitySummary = async (data) => { + const requiredFields = ['coin']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const coin = data.coin; + const url = `/crosschain/txactivity?foreignBlockchain=${coin}`; // No apiKey here + + try { + const endpoint = await createEndpoint(url); + const response = await fetch(endpoint, { + method: 'POST', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_generic', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + if (res?.error && res?.message) { + throw new Error(res.message); + } + return res; // Return full response here + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.transaction_activity_summary', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const getForeignFee = async (data) => { + const requiredFields = ['coin', 'type']; + const missingFields: string[] = []; + + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const { coin, type } = data; + const url = `/crosschain/${coin.toLowerCase()}/${type}`; + + try { + const endpoint = await createEndpoint(url); + const response = await fetch(endpoint, { + method: 'GET', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_generic', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + if (res?.error && res?.message) { + throw new Error(res.message); + } + return res; // Return full response here + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.get_foreign_fee', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +function calculateRateFromFee(totalFee, sizeInBytes) { + const fee = (totalFee / sizeInBytes) * 1000; + return fee.toFixed(0); +} + +export const updateForeignFee = async (data, isFromExtension) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const requiredFields = ['coin', 'type', 'value']; + const missingFields: string[] = []; + + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const { coin, type, value } = data; + + const text3 = + type === 'feerequired' + ? i18n.t('question:sats', { + amount: value, + postProcess: 'capitalizeFirstChar', + }) + : i18n.t('question:sats_per_kb', { + amount: value, + postProcess: 'capitalizeFirstChar', + }); + const text4 = + type === 'feerequired' + ? i18n.t('question:message.generic.calculate_fee', { + amount: value, + rate: calculateRateFromFee(value, 300), + postProcess: 'capitalizeFirstChar', + }) + : ''; + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.update_foreign_fee', { + postProcess: 'capitalizeFirstChar', + }), + text2: `type: ${type === 'feerequired' ? 'unlocking' : 'locking'}`, + text3: i18n.t('question:value', { + value: text3, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('question:coin', { + coin: coin, + postProcess: 'capitalizeFirstChar', + }), + }, + isFromExtension + ); + + const { accepted } = resPermission; + if (!accepted) { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const url = `/crosschain/${coin.toLowerCase()}/update${type}`; + const valueStringified = JSON.stringify(+value); + + const endpoint = await createEndpoint(url); + const response = await fetch(endpoint, { + method: 'POST', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + body: valueStringified, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.update_foreign_fee', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + if (res?.error && res?.message) { + throw new Error(res.message); + } + return res; // Return full response here +}; + +export const getServerConnectionHistory = async (data) => { + const requiredFields = ['coin']; + const missingFields: string[] = []; + + // Validate required fields + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const coin = data.coin.toLowerCase(); + const url = `/crosschain/${coin.toLowerCase()}/serverconnectionhistory`; + + try { + const endpoint = await createEndpoint(url); // Assuming createEndpoint is available + const response = await fetch(endpoint, { + method: 'GET', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_connection_history', { + postProcess: 'capitalizeFirstChar', + }) + ); + + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + + if (res?.error && res?.message) { + throw new Error(res.message); + } + + return res; // Return full response here + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.fetch_connection_history', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const setCurrentForeignServer = async (data, isFromExtension) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const requiredFields = ['coin']; + const missingFields: string[] = []; + + // Validate required fields + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const { coin, host, port, type } = data; + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.set_current_server', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:server_type', { + type: type, + postProcess: 'capitalizeFirstChar', + }), + text3: i18n.t('question:server_host', { + host: host, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('question:coin', { + coin: coin, + postProcess: 'capitalizeFirstChar', + }), + }, + isFromExtension + ); + + const { accepted } = resPermission; + if (!accepted) { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const body = { + hostName: host, + port: port, + connectionType: type, + }; + + const url = `/crosschain/${coin.toLowerCase()}/setcurrentserver`; + + const endpoint = await createEndpoint(url); // Assuming createEndpoint is available + const response = await fetch(endpoint, { + method: 'POST', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.server_current_set', { + postProcess: 'capitalizeFirstChar', + }) + ); + + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + + if (res?.error && res?.message) { + throw new Error(res.message); + } + + return res; // Return the full response +}; + +export const addForeignServer = async (data, isFromExtension) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const requiredFields = ['coin']; + const missingFields: string[] = []; + + // Validate required fields + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const { coin, host, port, type } = data; + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.server_add', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:server_type', { + type: type, + postProcess: 'capitalizeFirstChar', + }), + text3: i18n.t('question:server_host', { + host: host, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('question:coin', { + coin: coin, + postProcess: 'capitalizeFirstChar', + }), + }, + isFromExtension + ); + + const { accepted } = resPermission; + if (!accepted) { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const body = { + hostName: host, + port: port, + connectionType: type, + }; + + const url = `/crosschain/${coin.toLowerCase()}/addserver`; + + const endpoint = await createEndpoint(url); // Assuming createEndpoint is available + const response = await fetch(endpoint, { + method: 'POST', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.server_current_add', { + postProcess: 'capitalizeFirstChar', + }) + ); + + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + + if (res?.error && res?.message) { + throw new Error(res.message); + } + + return res; // Return the full response +}; + +export const removeForeignServer = async (data, isFromExtension) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const requiredFields = ['coin']; + const missingFields: string[] = []; + + // Validate required fields + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const { coin, host, port, type } = data; + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.server_remove', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:server_type', { + type: type, + postProcess: 'capitalizeFirstChar', + }), + text3: i18n.t('question:server_host', { + host: host, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('question:coin', { + coin: coin, + postProcess: 'capitalizeFirstChar', + }), + }, + isFromExtension + ); + + const { accepted } = resPermission; + if (!accepted) { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const body = { + hostName: host, + port: port, + connectionType: type, + }; + + const url = `/crosschain/${coin.toLowerCase()}/removeserver`; + + const endpoint = await createEndpoint(url); // Assuming createEndpoint is available + const response = await fetch(endpoint, { + method: 'POST', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.server_remove', { + postProcess: 'capitalizeFirstChar', + }) + ); + + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + + if (res?.error && res?.message) { + throw new Error(res.message); + } + + return res; // Return the full response +}; + +export const getDaySummary = async () => { + const url = `/admin/summary`; // Simplified endpoint URL + + try { + const endpoint = await createEndpoint(url); // Assuming createEndpoint is available for constructing the full URL + const response = await fetch(endpoint, { + method: 'GET', + headers: { + Accept: '*/*', + }, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.retrieve_summary', { + postProcess: 'capitalizeFirstChar', + }) + ); + + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + + if (res?.error && res?.message) { + throw new Error(res.message); + } + + return res; // Return the full response + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.retrieve_summary', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const getNodeInfo = async () => { + const url = `/admin/info`; // Simplified endpoint URL + + try { + const endpoint = await createEndpoint(url); // Assuming createEndpoint is available for constructing the full URL + const response = await fetch(endpoint, { + method: 'GET', + headers: { + Accept: '*/*', + }, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.node_info', { + postProcess: 'capitalizeFirstChar', + }) + ); + + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + + if (res?.error && res?.message) { + throw new Error(res.message); + } + + return res; // Return the full response + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.node_info', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const getNodeStatus = async () => { + const url = `/admin/status`; // Simplified endpoint URL + + try { + const endpoint = await createEndpoint(url); // Assuming createEndpoint is available for constructing the full URL + const response = await fetch(endpoint, { + method: 'GET', + headers: { + Accept: '*/*', + }, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.node_status', { + postProcess: 'capitalizeFirstChar', + }) + ); + + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + + if (res?.error && res?.message) { + throw new Error(res.message); + } + + return res; // Return the full response + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.node_status', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const getArrrSyncStatus = async () => { + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const arrrSeed = parsedData.arrrSeed58; + const url = `/crosschain/arrr/syncstatus`; // Simplified endpoint URL + + try { + const endpoint = await createEndpoint(url); // Assuming createEndpoint is available for constructing the full URL + const response = await fetch(endpoint, { + method: 'POST', + headers: { + Accept: '*/*', + }, + body: arrrSeed, + }); + + let res; + + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + + return res; // Return the full response + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.retrieve_sync_status', { + token: 'ARRR', + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const sendCoin = async (data, isFromExtension) => { + const requiredFields = ['coin', 'amount']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + if (!data?.destinationAddress && !data?.recipient) { + throw new Error( + i18n.t('question:message.error.missing_fields', { + fields: 'recipient', + postProcess: 'capitalizeFirstChar', + }) + ); + } + let checkCoin = data.coin; + const wallet = await getSaveWallet(); + const address = wallet.address0; + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const isGateway = await isRunningGateway(); + + if (checkCoin !== 'QORT' && isGateway) + throw new Error( + i18n.t('question:message.error.gateway_non_qort_local_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + if (checkCoin === 'QORT') { + // Params: data.coin, data.recipient, data.amount, data.fee + // TODO: prompt user to send. If they confirm, call `POST /crosschain/:coin/send`, or for QORT, broadcast a PAYMENT transaction + // then set the response string from the core to the `response` variable (defined above) + // If they decline, send back JSON that includes an `error` key, such as `{"error": "User declined request"}` + const amount = Number(data.amount); + const recipient = data?.recipient || data.destinationAddress; + + const url = await createEndpoint(`/addresses/balance/${address}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_balance', { + postProcess: 'capitalizeFirstChar', + }) + ); + let walletBalance; + try { + walletBalance = await response.clone().json(); + } catch (e) { + walletBalance = await response.text(); + } + if (isNaN(Number(walletBalance))) { + const errorMsg = i18n.t('question:message.error.fetch_balance_token', { + token: 'QORT', + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const transformDecimals = (Number(walletBalance) * QORT_DECIMALS).toFixed( + 0 + ); + const walletBalanceDecimals = Number(transformDecimals); + const amountDecimals = Number(amount) * QORT_DECIMALS; + const fee: number = await sendQortFee(); + if (amountDecimals + fee * QORT_DECIMALS > walletBalanceDecimals) { + const errorMsg = i18n.t('question:message.error.insufficient_funds', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + if (amount <= 0) { + const errorMsg = i18n.t('core:message.error.invalid_amount', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + if (recipient.length === 0) { + const errorMsg = i18n.t('question:message.error.empty_receiver', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.send_coins', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:to_recipient', { + recipient: recipient, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `${amount} ${checkCoin}`, + fee: fee, + confirmCheckbox: true, + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const makePayment = await sendCoinFunc( + { amount, password: null, receiver: recipient }, + true + ); + return makePayment.res?.data; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else if (checkCoin === 'BTC') { + const amount = Number(data.amount); + const recipient = data?.recipient || data.destinationAddress; + const xprv58 = parsedData.btcPrivateKey; + const feePerByte = data.fee ? data.fee : btcFeePerByte; + + const btcWalletBalance = await getWalletBalance({ coin: checkCoin }, true); + + if (isNaN(Number(btcWalletBalance))) { + throw new Error( + i18n.t('question:message.error.fetch_balance_token', { + token: 'BTC', + postProcess: 'capitalizeFirstChar', + }) + ); + } + const btcWalletBalanceDecimals = Number(btcWalletBalance); + const btcAmountDecimals = Number(amount); + const fee = feePerByte * 500; // default 0.00050000 + if (btcAmountDecimals + fee > btcWalletBalanceDecimals) { + throw new Error( + i18n.t('question:message.error.insufficient_funds', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.send_coins', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:to_recipient', { + recipient: recipient, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `${amount} ${checkCoin}`, + foreignFee: `${fee} BTC`, + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const opts = { + xprv58: xprv58, + receivingAddress: recipient, + bitcoinAmount: amount, + feePerByte: feePerByte, + }; + const url = await createEndpoint(`/crosschain/btc/send`); + + const response = await fetch(url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(opts), + }); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.send', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else if (checkCoin === 'LTC') { + const amount = Number(data.amount); + const recipient = data?.recipient || data.destinationAddress; + const xprv58 = parsedData.ltcPrivateKey; + const feePerByte = data.fee ? data.fee : ltcFeePerByte; + const ltcWalletBalance = await getWalletBalance({ coin: checkCoin }, true); + + if (isNaN(Number(ltcWalletBalance))) { + const errorMsg = i18n.t('question:message.error.fetch_balance_token', { + token: 'LTC', + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const ltcWalletBalanceDecimals = Number(ltcWalletBalance); + const ltcAmountDecimals = Number(amount); + const fee = feePerByte * 1000; // default 0.00030000 + if (ltcAmountDecimals + fee > ltcWalletBalanceDecimals) { + throw new Error( + i18n.t('question:message.error.insufficient_funds', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.send_coins', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:to_recipient', { + recipient: recipient, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `${amount} ${checkCoin}`, + foreignFee: `${fee} LTC`, + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const url = await createEndpoint(`/crosschain/ltc/send`); + const opts = { + xprv58: xprv58, + receivingAddress: recipient, + litecoinAmount: amount, + feePerByte: feePerByte, + }; + const response = await fetch(url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(opts), + }); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.send', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else if (checkCoin === 'DOGE') { + const amount = Number(data.amount); + const recipient = data?.recipient || data.destinationAddress; + const xprv58 = parsedData.dogePrivateKey; + const feePerByte = data.fee ? data.fee : dogeFeePerByte; + const dogeWalletBalance = await getWalletBalance({ coin: checkCoin }, true); + if (isNaN(Number(dogeWalletBalance))) { + const errorMsg = i18n.t('question:message.error.fetch_balance_token', { + token: 'DOGE', + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const dogeWalletBalanceDecimals = Number(dogeWalletBalance); + const dogeAmountDecimals = Number(amount); + const fee = feePerByte * 5000; // default 0.05000000 + if (dogeAmountDecimals + fee > dogeWalletBalanceDecimals) { + const errorMsg = i18n.t('question:message.error.insufficient_funds', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.send_coins', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:to_recipient', { + recipient: recipient, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `${amount} ${checkCoin}`, + foreignFee: `${fee} DOGE`, + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const opts = { + xprv58: xprv58, + receivingAddress: recipient, + dogecoinAmount: amount, + feePerByte: feePerByte, + }; + const url = await createEndpoint(`/crosschain/doge/send`); + + const response = await fetch(url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(opts), + }); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.send', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else if (checkCoin === 'DGB') { + const amount = Number(data.amount); + const recipient = data?.recipient || data.destinationAddress; + const xprv58 = parsedData.dbgPrivateKey; + const feePerByte = data.fee ? data.fee : dgbFeePerByte; + const dgbWalletBalance = await getWalletBalance({ coin: checkCoin }, true); + if (isNaN(Number(dgbWalletBalance))) { + const errorMsg = i18n.t('question:message.error.fetch_balance_token', { + token: 'DGB', + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const dgbWalletBalanceDecimals = Number(dgbWalletBalance); + const dgbAmountDecimals = Number(amount); + const fee = feePerByte * 500; // default 0.00005000 + if (dgbAmountDecimals + fee > dgbWalletBalanceDecimals) { + const errorMsg = i18n.t('question:message.error.insufficient_funds', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.send_coins', { + postProcess: 'capitalizeFirstChar', + }), + text2: `To: ${recipient}`, + highlightedText: `${amount} ${checkCoin}`, + foreignFee: `${fee} DGB`, + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const opts = { + xprv58: xprv58, + receivingAddress: recipient, + digibyteAmount: amount, + feePerByte: feePerByte, + }; + const url = await createEndpoint(`/crosschain/dgb/send`); + + const response = await fetch(url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(opts), + }); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.send', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else if (checkCoin === 'RVN') { + const amount = Number(data.amount); + const recipient = data?.recipient || data.destinationAddress; + const xprv58 = parsedData.rvnPrivateKey; + const feePerByte = data.fee ? data.fee : rvnFeePerByte; + const rvnWalletBalance = await getWalletBalance({ coin: checkCoin }, true); + if (isNaN(Number(rvnWalletBalance))) { + const errorMsg = i18n.t('question:message.error.fetch_balance_token', { + token: 'RVN', + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const rvnWalletBalanceDecimals = Number(rvnWalletBalance); + const rvnAmountDecimals = Number(amount); + const fee = feePerByte * 500; // default 0.00562500 + if (rvnAmountDecimals + fee > rvnWalletBalanceDecimals) { + const errorMsg = i18n.t('question:message.error.insufficient_funds', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.send_coins', { + postProcess: 'capitalizeFirstChar', + }), + text2: `To: ${recipient}`, + highlightedText: `${amount} ${checkCoin}`, + foreignFee: `${fee} RVN`, + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const opts = { + xprv58: xprv58, + receivingAddress: recipient, + ravencoinAmount: amount, + feePerByte: feePerByte, + }; + const url = await createEndpoint(`/crosschain/rvn/send`); + + const response = await fetch(url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(opts), + }); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.send', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else if (checkCoin === 'ARRR') { + const amount = Number(data.amount); + const recipient = data?.recipient || data.destinationAddress; + const memo = data?.memo; + const arrrWalletBalance = await getWalletBalance({ coin: checkCoin }, true); + + if (isNaN(Number(arrrWalletBalance))) { + const errorMsg = i18n.t('question:message.error.fetch_balance_token', { + token: 'ARR', + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const arrrWalletBalanceDecimals = Number(arrrWalletBalance); + const arrrAmountDecimals = Number(amount); + const fee = 0.0001; + if (arrrAmountDecimals + fee > arrrWalletBalanceDecimals) { + const errorMsg = i18n.t('question:message.error.insufficient_funds', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.send_coins', { + postProcess: 'capitalizeFirstChar', + }), + text2: `To: ${recipient}`, + highlightedText: `${amount} ${checkCoin}`, + foreignFee: `${fee} ARRR`, + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const opts = { + entropy58: parsedData.arrrSeed58, + receivingAddress: recipient, + arrrAmount: amount, + memo: memo, + }; + const url = await createEndpoint(`/crosschain/arrr/send`); + + const response = await fetch(url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(opts), + }); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.send', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } +}; + +function calculateFeeFromRate(feePerKb, sizeInBytes) { + return (feePerKb / 1000) * sizeInBytes; +} + +const getBuyingFees = async (foreignBlockchain) => { + const ticker = sellerForeignFee[foreignBlockchain].ticker; + if (!ticker) throw new Error('invalid foreign blockchain'); + const unlockFee = await getForeignFee({ + coin: ticker, + type: 'feerequired', + }); + const lockFee = await getForeignFee({ + coin: ticker, + type: 'feekb', + }); + return { + ticker: ticker, + lock: { + sats: lockFee, + fee: lockFee / QORT_DECIMALS, + }, + unlock: { + sats: unlockFee, + fee: unlockFee / QORT_DECIMALS, + feePerKb: +calculateRateFromFee(+unlockFee, 300) / QORT_DECIMALS, + }, + }; +}; + +export const createBuyOrder = async (data, isFromExtension) => { + const requiredFields = ['crosschainAtInfo', 'foreignBlockchain']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const isGateway = await isRunningGateway(); + const foreignBlockchain = data.foreignBlockchain; + const atAddresses = data.crosschainAtInfo?.map( + (order) => order.qortalAtAddress + ); + + const atPromises = atAddresses.map((atAddress) => + requestQueueGetAtAddresses.enqueue(async () => { + const url = await createEndpoint(`/crosschain/trade/${atAddress}`); + const resAddress = await fetch(url); + const resData = await resAddress.json(); + if (foreignBlockchain !== resData?.foreignBlockchain) { + throw new Error( + i18n.t('core:message.error.same_foreign_blockchain', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + return resData; + }) + ); + + const crosschainAtInfo = await Promise.all(atPromises); + + try { + const buyingFees = await getBuyingFees(foreignBlockchain); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.buy_order', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:permission.buy_order_quantity', { + quantity: atAddresses?.length, + postProcess: 'capitalizeFirstChar', + }), + text3: i18n.t('question:permission.buy_order_ticker', { + qort_amount: crosschainAtInfo?.reduce((latest, cur) => { + return latest + +cur?.qortAmount; + }, 0), + foreign_amount: roundUpToDecimals( + crosschainAtInfo?.reduce((latest, cur) => { + return latest + +cur?.expectedForeignAmount; + }, 0) + ), + ticker: buyingFees.ticker, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('auth:node.using_public_gateway', { + gateway: isGateway, + postProcess: 'capitalizeFirstChar', + }), + fee: '', + html: ` +
+ + +
+
${i18n.t('question:total_unlocking_fee', { + postProcess: 'capitalizeFirstChar', + })}
+
${(+buyingFees?.unlock?.fee * atAddresses?.length)?.toFixed(8)} ${buyingFees.ticker}
+
+ ${i18n.t('question:permission.buy_order_fee_estimation', { + quantity: atAddresses?.length, + fee: buyingFees?.unlock?.feePerKb?.toFixed(8), + ticker: buyingFees.ticker, + postProcess: 'capitalizeFirstChar', + })} +
+
${i18n.t('question:total_locking_fee', { + postProcess: 'capitalizeFirstChar', + })}
+
${i18n.t('question:permission.buy_order_per_kb', { + fee: +buyingFees?.lock.fee.toFixed(8), + ticker: buyingFees.ticker, + postProcess: 'capitalizeFirstChar', + })} +
+
+
+`, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const resBuyOrder = await createBuyOrderTx({ + crosschainAtInfo, + isGateway, + foreignBlockchain, + }); + return resBuyOrder; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.buy_order', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +const cancelTradeOfferTradeBot = async (body, keyPair) => { + const txn = new DeleteTradeOffer().createTransaction(body); + const url = await createEndpoint(`/crosschain/tradeoffer`); + const bodyToString = JSON.stringify(txn); + + const deleteTradeBotResponse = await fetch(url, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + body: bodyToString, + }); + + if (!deleteTradeBotResponse.ok) { + throw new Error( + i18n.t('question:message.error.update_tradebot', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + const unsignedTxn = await deleteTradeBotResponse.text(); + const signedTxnBytes = await signTradeBotTransaction(unsignedTxn, keyPair); + const signedBytes = Base58.encode(signedTxnBytes); + + let res; + try { + res = await processTransactionVersion2(signedBytes); + } catch (error) { + return { + error: i18n.t('question:message.error.cancel_sell_order', { + postProcess: 'capitalizeFirstChar', + }), + failedTradeBot: { + atAddress: body.atAddress, + creatorAddress: body.creatorAddress, + }, + }; + } + if (res?.error) { + return { + error: i18n.t('question:message.error.cancel_sell_order', { + postProcess: 'capitalizeFirstChar', + }), + failedTradeBot: { + atAddress: body.atAddress, + creatorAddress: body.creatorAddress, + }, + }; + } + if (res?.signature) { + return res; + } else { + throw new Error( + i18n.t('question:message.error.cancel_sell_order', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; +const findFailedTradebot = async (createBotCreationTimestamp, body) => { + //wait 5 secs + const wallet = await getSaveWallet(); + const address = wallet.address0; + await new Promise((res) => { + setTimeout(() => { + res(null); + }, 5000); + }); + const url = await createEndpoint( + `/crosschain/tradebot?foreignBlockchain=LITECOIN` + ); + + const tradeBotsReponse = await fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + const data = await tradeBotsReponse.json(); + const latestItem2 = data + .filter((item) => item.creatorAddress === address) + .sort((a, b) => b.timestamp - a.timestamp)[0]; + const latestItem = data + .filter( + (item) => + item.creatorAddress === address && + +item.foreignAmount === +body.foreignAmount + ) + .sort((a, b) => b.timestamp - a.timestamp)[0]; + if ( + latestItem && + createBotCreationTimestamp - latestItem.timestamp <= 5000 && + createBotCreationTimestamp > latestItem.timestamp // Ensure latestItem's timestamp is before createBotCreationTimestamp + ) { + return latestItem; + } else { + return null; + } +}; +const tradeBotCreateRequest = async (body, keyPair) => { + const txn = new TradeBotCreateRequest().createTransaction(body); + const url = await createEndpoint(`/crosschain/tradebot/create`); + const bodyToString = JSON.stringify(txn); + + const unsignedTxnResponse = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: bodyToString, + }); + if (!unsignedTxnResponse.ok) + throw new Error( + i18n.t('question:message.error.create_tradebot', { + postProcess: 'capitalizeFirstChar', + }) + ); + const createBotCreationTimestamp = Date.now(); + const unsignedTxn = await unsignedTxnResponse.text(); + const signedTxnBytes = await signTradeBotTransaction(unsignedTxn, keyPair); + const signedBytes = Base58.encode(signedTxnBytes); + + let res; + try { + res = await processTransactionVersion2(signedBytes); + } catch (error) { + const findFailedTradeBot = await findFailedTradebot( + createBotCreationTimestamp, + body + ); + return { + error: i18n.t('question:message.error.create_sell_order', { + postProcess: 'capitalizeFirstChar', + }), + failedTradeBot: findFailedTradeBot, + }; + } + + if (res?.signature) { + return res; + } else { + throw new Error( + i18n.t('question:message.error.create_sell_order', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const createSellOrder = async (data, isFromExtension) => { + const requiredFields = ['qortAmount', 'foreignBlockchain', 'foreignAmount']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const parsedForeignAmount = Number(data.foreignAmount)?.toFixed(8); + + const receivingAddress = await getUserWalletFunc(data.foreignBlockchain); + try { + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.sell_order', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:permission.order_detail', { + qort_amount: data.qortAmount, + foreign_amount: parsedForeignAmount, + ticker: data.foreignBlockchain, + postProcess: 'capitalizeFirstChar', + }), + fee: '0.02', + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const userPublicKey = parsedData.publicKey; + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey, + }; + const response = await tradeBotCreateRequest( + { + creatorPublicKey: userPublicKey, + qortAmount: parseFloat(data.qortAmount), + fundingQortAmount: parseFloat(data.qortAmount) + 0.01, + foreignBlockchain: data.foreignBlockchain, + foreignAmount: parseFloat(parsedForeignAmount), + tradeTimeout: 120, + receivingAddress: receivingAddress.address, + }, + keyPair + ); + + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.submit_sell_order', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const cancelSellOrder = async (data, isFromExtension) => { + const requiredFields = ['atAddress']; + const missingFields: string[] = []; + + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const url = await createEndpoint(`/crosschain/trade/${data.atAddress}`); + const resAddress = await fetch(url); + const resData = await resAddress.json(); + + if (!resData?.qortalAtAddress) + throw new Error( + i18n.t('question:message.error.at_info', { + postProcess: 'capitalizeFirstChar', + }) + ); + + try { + const fee = await getFee('MESSAGE'); + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.cancel_sell_order', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:permission.order_detail', { + qort_amount: resData.qortAmount, + foreign_amount: resData.expectedForeignAmount, + ticker: resData.foreignBlockchain, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const userPublicKey = parsedData.publicKey; + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey, + }; + const response = await cancelTradeOfferTradeBot( + { + creatorPublicKey: userPublicKey, + atAddress: data.atAddress, + }, + keyPair + ); + + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.submit_sell_order', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const openNewTab = async (data, isFromExtension) => { + const requiredFields = ['qortalLink']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const res = extractComponents(data.qortalLink); + if (res) { + const { service, name, identifier, path } = res; + if (!service && !name) + throw new Error( + i18n.t('auth:message.error.invalid_qortal_link', { + postProcess: 'capitalizeFirstChar', + }) + ); + executeEvent('addTab', { data: { service, name, identifier, path } }); + executeEvent('open-apps-mode', {}); + return true; + } else { + throw new Error( + i18n.t('auth:message.error.invalid_qortal_link', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const adminAction = async (data, isFromExtension) => { + const requiredFields = ['type']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + // For actions that require a value, check for 'value' field + const actionsRequiringValue = [ + 'addpeer', + 'removepeer', + 'forcesync', + 'addmintingaccount', + 'removemintingaccount', + ]; + if (actionsRequiringValue.includes(data.type.toLowerCase()) && !data.value) { + missingFields.push('value'); + } + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + let apiEndpoint = ''; + let method = 'GET'; // Default method + let includeValueInBody = false; + switch (data.type.toLowerCase()) { + case 'stop': + apiEndpoint = await createEndpoint('/admin/stop'); + break; + case 'restart': + apiEndpoint = await createEndpoint('/admin/restart'); + break; + case 'bootstrap': + apiEndpoint = await createEndpoint('/admin/bootstrap'); + break; + case 'addmintingaccount': + apiEndpoint = await createEndpoint('/admin/mintingaccounts'); + method = 'POST'; + includeValueInBody = true; + break; + case 'removemintingaccount': + apiEndpoint = await createEndpoint('/admin/mintingaccounts'); + method = 'DELETE'; + includeValueInBody = true; + break; + case 'forcesync': + apiEndpoint = await createEndpoint('/admin/forcesync'); + method = 'POST'; + includeValueInBody = true; + break; + case 'addpeer': + apiEndpoint = await createEndpoint('/peers'); + method = 'POST'; + includeValueInBody = true; + break; + case 'removepeer': + apiEndpoint = await createEndpoint('/peers'); + method = 'DELETE'; + includeValueInBody = true; + break; + default: + throw new Error( + i18n.t('question:message.error.unknown_admin_action_type', { + type: data.type, + postProcess: 'capitalizeFirstChar', + }) + ); + } + // Prepare the permission prompt text + let permissionText = i18n.t('question:permission.perform_admin_action', { + type: data.type, + postProcess: 'capitalizeFirstChar', + }); + + if (data.value) { + permissionText += + ' ' + + i18n.t('question:permission.perform_admin_action_with_value', { + value: data.value, + postProcess: 'capitalizeFirstChar', + }); + } + + const resPermission = await getUserPermission( + { + text1: permissionText, + }, + isFromExtension + ); + + const { accepted } = resPermission; + + if (accepted) { + // Set up options for the API call + const options: RequestInit = { + method: method, + headers: {}, + }; + if (includeValueInBody) { + options.headers['Content-Type'] = 'text/plain'; + options.body = data.value; + } + const response = await fetch(apiEndpoint, options); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.perform_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const signTransaction = async (data, isFromExtension) => { + const requiredFields = ['unsignedBytes']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const shouldProcess = data?.process || false; + const _url = await createEndpoint( + '/transactions/decode?ignoreValidityChecks=false' + ); + + const _body = data.unsignedBytes; + const response = await fetch(_url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: _body, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.decode_transaction', { + postProcess: 'capitalizeFirstChar', + }) + ); + const decodedData = await response.json(); + const resPermission = await getUserPermission( + { + text1: shouldProcess + ? i18n.t('question:permission.sign_process_transaction', { + postProcess: 'capitalizeFirstChar', + }) + : i18n.t('question:permission.sign_transaction', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t( + 'question:message.generic.read_transaction_carefully', + { postProcess: 'capitalizeFirstChar' } + ), + text2: `Tx type: ${decodedData.type}`, + json: decodedData, + }, + isFromExtension + ); + + const { accepted } = resPermission; + if (accepted) { + let urlConverted = await createEndpoint('/transactions/convert'); + + const responseConverted = await fetch(urlConverted, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: data.unsignedBytes, + }); + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey, + }; + const convertedBytes = await responseConverted.text(); + const txBytes = Base58.decode(data.unsignedBytes); + const _arbitraryBytesBuffer = Object.keys(txBytes).map(function (key) { + return txBytes[key]; + }); + const arbitraryBytesBuffer = new Uint8Array(_arbitraryBytesBuffer); + const txByteSigned = Base58.decode(convertedBytes); + const _bytesForSigningBuffer = Object.keys(txByteSigned).map( + function (key) { + return txByteSigned[key]; + } + ); + const bytesForSigningBuffer = new Uint8Array(_bytesForSigningBuffer); + const signature = nacl.sign.detached( + bytesForSigningBuffer, + keyPair.privateKey + ); + const signedBytes = utils.appendBuffer(arbitraryBytesBuffer, signature); + const signedBytesToBase58 = Base58.encode(signedBytes); + if (!shouldProcess) { + return signedBytesToBase58; + } + const res = await processTransactionVersion2(signedBytesToBase58); + if (!res?.signature) + throw new Error( + res?.message || + i18n.t('question:message.error.process_transaction', { + postProcess: 'capitalizeFirstChar', + }) + ); + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +const missingFieldsFunc = (data, requiredFields) => { + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } +}; + +const encode = (value) => encodeURIComponent(value.trim()); // Helper to encode values +const buildQueryParams = (data) => { + const allowedParams = [ + 'name', + 'service', + 'identifier', + 'mimeType', + 'fileName', + 'encryptionType', + 'key', + ]; + return Object.entries(data) + .map(([key, value]) => { + if ( + value === undefined || + value === null || + value === false || + !allowedParams.includes(key) + ) + return null; // Skip null, undefined, or false + if (typeof value === 'boolean') return `${key}=${value}`; // Handle boolean values + return `${key}=${encode(value)}`; // Encode other values + }) + .filter(Boolean) // Remove null values + .join('&'); // Join with `&` +}; +export const createAndCopyEmbedLink = async (data, isFromExtension) => { + const requiredFields = ['type']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + switch (data.type) { + case 'POLL': { + missingFieldsFunc(data, ['type', 'name']); + + const queryParams = [ + `name=${encode(data.name)}`, + data.ref ? `ref=${encode(data.ref)}` : null, // Add only if ref exists + ] + .filter(Boolean) // Remove null values + .join('&'); // Join with `&` + const link = `qortal://use-embed/POLL?${queryParams}`; + try { + await navigator.clipboard.writeText(link); + } catch (error) { + throw new Error( + i18n.t('question:message.error.copy_clipboard', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + return link; + } + case 'IMAGE': + case 'ATTACHMENT': { + missingFieldsFunc(data, ['type', 'name', 'service', 'identifier']); + if (data?.encryptionType === 'private' && !data?.key) { + throw new Error( + i18n.t('question:message.generic.provide_key_shared_link', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const queryParams = buildQueryParams(data); + + const link = `qortal://use-embed/${data.type}?${queryParams}`; + + try { + await navigator.clipboard.writeText(link); + } catch (error) { + throw new Error( + i18n.t('question:message.error.copy_clipboard', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + return link; + } + + default: + throw new Error( + i18n.t('question:message.error.invalid_type', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const registerNameRequest = async (data, isFromExtension) => { + const requiredFields = ['name']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const fee = await getFee('REGISTER_NAME'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.register_name', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: data.name, + text2: data?.description, + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const name = data.name; + const description = data?.description || ''; + const response = await registerName({ name, description }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const updateNameRequest = async (data, isFromExtension) => { + const requiredFields = ['newName', 'oldName']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const oldName = data.oldName; + const newName = data.newName; + const description = data?.description || ''; + const fee = await getFee('UPDATE_NAME'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.register_name', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: data.newName, + text2: data?.description, + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await updateName({ oldName, newName, description }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const leaveGroupRequest = async (data, isFromExtension) => { + const requiredFields = ['groupId']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupId = data.groupId; + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const fee = await getFee('LEAVE_GROUP'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.leave_group', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `${groupInfo.groupName}`, + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await leaveGroup({ groupId }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const inviteToGroupRequest = async (data, isFromExtension) => { + const requiredFields = ['groupId', 'inviteTime', 'inviteeAddress']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupId = data.groupId; + const qortalAddress = data?.inviteeAddress; + const inviteTime = data?.inviteTime; + + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const displayInvitee = await getNameInfoForOthers(qortalAddress); + + const fee = await getFee('GROUP_INVITE'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.invite', { + invitee: displayInvitee || qortalAddress, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('group:group.group_name', { + name: groupInfo?.groupName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await inviteToGroup({ + groupId, + qortalAddress, + inviteTime, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const kickFromGroupRequest = async (data, isFromExtension) => { + const requiredFields = ['groupId', 'qortalAddress']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupId = data.groupId; + const qortalAddress = data?.qortalAddress; + const reason = data?.reason; + + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const displayInvitee = await getNameInfoForOthers(qortalAddress); + + const fee = await getFee('GROUP_KICK'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.kick', { + partecipant: displayInvitee || qortalAddress, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('group:group.group_name', { + name: groupInfo?.groupName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await kickFromGroup({ + groupId, + qortalAddress, + rBanReason: reason, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const banFromGroupRequest = async (data, isFromExtension) => { + const requiredFields = ['groupId', 'qortalAddress']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupId = data.groupId; + const qortalAddress = data?.qortalAddress; + const rBanTime = data?.banTime; + const reason = data?.reason; + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const displayInvitee = await getNameInfoForOthers(qortalAddress); + + const fee = await getFee('GROUP_BAN'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.ban', { + partecipant: displayInvitee || qortalAddress, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('group:group.group_name', { + name: groupInfo?.groupName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await banFromGroup({ + groupId, + qortalAddress, + rBanTime, + rBanReason: reason, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const cancelGroupBanRequest = async (data, isFromExtension) => { + const requiredFields = ['groupId', 'qortalAddress']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupId = data.groupId; + const qortalAddress = data?.qortalAddress; + + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const displayInvitee = await getNameInfoForOthers(qortalAddress); + + const fee = await getFee('CANCEL_GROUP_BAN'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.cancel_ban', { + partecipant: displayInvitee || qortalAddress, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('group:group.group_name', { + name: groupInfo?.groupName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await cancelBan({ + groupId, + qortalAddress, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const addGroupAdminRequest = async (data, isFromExtension) => { + const requiredFields = ['groupId', 'qortalAddress']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupId = data.groupId; + const qortalAddress = data?.qortalAddress; + + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const displayInvitee = await getNameInfoForOthers(qortalAddress); + + const fee = await getFee('ADD_GROUP_ADMIN'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.add_admin', { + invitee: displayInvitee || qortalAddress, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('group:group.group_name', { + name: groupInfo?.groupName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await makeAdmin({ + groupId, + qortalAddress, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const removeGroupAdminRequest = async (data, isFromExtension) => { + const requiredFields = ['groupId', 'qortalAddress']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupId = data.groupId; + const qortalAddress = data?.qortalAddress; + + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const displayInvitee = await getNameInfoForOthers(qortalAddress); + + const fee = await getFee('REMOVE_GROUP_ADMIN'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.remove_admin', { + partecipant: displayInvitee || qortalAddress, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('group:group.group_name', { + name: groupInfo?.groupName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await removeAdmin({ + groupId, + qortalAddress, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const cancelGroupInviteRequest = async (data, isFromExtension) => { + const requiredFields = ['groupId', 'qortalAddress']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupId = data.groupId; + const qortalAddress = data?.qortalAddress; + + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const displayInvitee = await getNameInfoForOthers(qortalAddress); + + const fee = await getFee('CANCEL_GROUP_INVITE'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.cancel_group_invite', { + invitee: displayInvitee || qortalAddress, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('group:group.group_name', { + name: groupInfo?.groupName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + + const { accepted } = resPermission; + + if (accepted) { + const response = await cancelInvitationToGroup({ + groupId, + qortalAddress, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const createGroupRequest = async (data, isFromExtension) => { + const requiredFields = [ + 'approvalThreshold', + 'groupId', + 'groupName', + 'maxBlock', + 'minBlock', + 'qortalAddress', + 'type', + ]; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (data[field] !== undefined && data[field] !== null) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupName = data.groupName; + const description = data?.description || ''; + const type = +data.type; + const approvalThreshold = +data?.approvalThreshold; + const minBlock = +data?.minBlock; + const maxBlock = +data.maxBlock; + + const fee = await getFee('CREATE_GROUP'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.create_group', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('group:group.group_name', { + name: groupName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await createGroup({ + groupName, + groupDescription: description, + groupType: type, + groupApprovalThreshold: approvalThreshold, + minBlock, + maxBlock, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const updateGroupRequest = async (data, isFromExtension) => { + const requiredFields = [ + 'groupId', + 'newOwner', + 'type', + 'approvalThreshold', + 'minBlock', + 'maxBlock', + ]; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (data[field] !== undefined && data[field] !== null) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupId = +data.groupId; + const newOwner = data.newOwner; + const description = data?.description || ''; + const type = +data.type; + const approvalThreshold = +data?.approvalThreshold; + const minBlock = +data?.minBlock; + const maxBlock = +data.maxBlock; + + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const displayInvitee = await getNameInfoForOthers(newOwner); + + const fee = await getFee('CREATE_GROUP'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.update_group', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:permission.update_group_detail', { + owner: displayInvitee || newOwner, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('group:group.group_name', { + name: groupInfo?.groupName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await updateGroup({ + groupId, + newOwner, + newIsOpen: type, + newDescription: description, + newApprovalThreshold: approvalThreshold, + newMinimumBlockDelay: minBlock, + newMaximumBlockDelay: maxBlock, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const decryptAESGCMRequest = async (data, isFromExtension) => { + const requiredFields = ['encryptedData', 'iv', 'senderPublicKey']; + requiredFields.forEach((field) => { + if (!data[field]) { + throw new Error( + i18n.t('question:message.error.missing_fields', { + fields: field, + postProcess: 'capitalizeFirstChar', + }) + ); + } + }); + + const encryptedData = data.encryptedData; + const iv = data.iv; + const senderPublicKeyBase58 = data.senderPublicKey; + + // Decode keys and IV + const senderPublicKey = Base58.decode(senderPublicKeyBase58); + const resKeyPair = await getKeyPair(); // Assume this retrieves the current user's keypair + const uint8PrivateKey = Base58.decode(resKeyPair.privateKey); + + // Convert ed25519 keys to Curve25519 + const convertedPrivateKey = ed2curve.convertSecretKey(uint8PrivateKey); + const convertedPublicKey = ed2curve.convertPublicKey(senderPublicKey); + + // Generate shared secret + const sharedSecret = new Uint8Array(32); + nacl.lowlevel.crypto_scalarmult( + sharedSecret, + convertedPrivateKey, + convertedPublicKey + ); + + // Derive encryption key + const encryptionKey: Uint8Array = new Sha256() + .process(sharedSecret) + .finish().result; + + // Convert IV and ciphertext from Base64 + const base64ToUint8Array = (base64) => + Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); + const ivUint8Array = base64ToUint8Array(iv); + const ciphertext = base64ToUint8Array(encryptedData); + // Validate IV and key lengths + if (ivUint8Array.length !== 12) { + throw new Error( + i18n.t('question:message.error.invalid_encryption_iv', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + if (encryptionKey.length !== 32) { + throw new Error( + i18n.t('question:message.error.invalid_encryption_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + try { + // Decrypt data + const algorithm = { name: 'AES-GCM', iv: ivUint8Array }; + const cryptoKey = await crypto.subtle.importKey( + 'raw', + encryptionKey, + algorithm, + false, + ['decrypt'] + ); + const decryptedArrayBuffer = await crypto.subtle.decrypt( + algorithm, + cryptoKey, + ciphertext + ); + + // Return decrypted data as Base64 + return uint8ArrayToBase64(new Uint8Array(decryptedArrayBuffer)); + } catch (error) { + console.error('Decryption failed:', error); + throw new Error( + i18n.t('question:message.error.decrypt_message', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const sellNameRequest = async (data, isFromExtension) => { + const requiredFields = ['salePrice', 'nameForSale']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (data[field] !== undefined && data[field] !== null) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const name = data.nameForSale; + const sellPrice = +data.salePrice; + + const validApi = await getBaseApi(); + + const response = await fetch(validApi + '/names/' + name); + const nameData = await response.json(); + if (!nameData) + throw new Error( + i18n.t('auth:message.error.name_not_existing', { + postProcess: 'capitalizeFirstChar', + }) + ); + + if (nameData?.isForSale) + throw new Error( + i18n.t('question:message.error.name_already_for_sale', { + postProcess: 'capitalizeFirstChar', + }) + ); + const fee = await getFee('SELL_NAME'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.sell_name_transaction', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t( + 'question:permission.sell_name_transaction_detail', + { + name: name, + price: sellPrice, + postProcess: 'capitalizeFirstChar', + } + ), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await sellName({ + name, + sellPrice, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const cancelSellNameRequest = async (data, isFromExtension) => { + const requiredFields = ['nameForSale']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (data[field] !== undefined && data[field] !== null) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const name = data.nameForSale; + const validApi = await getBaseApi(); + + const response = await fetch(validApi + '/names/' + name); + const nameData = await response.json(); + if (!nameData?.isForSale) + throw new Error( + i18n.t('question:message.error.name_not_for_sale', { + postProcess: 'capitalizeFirstChar', + }) + ); + + const fee = await getFee('CANCEL_SELL_NAME'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.sell_name_cancel', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('question:name', { + name: name, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await cancelSellName({ + name, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const buyNameRequest = async (data, isFromExtension) => { + const requiredFields = ['nameForSale']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (data[field] !== undefined && data[field] !== null) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const name = data.nameForSale; + const validApi = await getBaseApi(); + const response = await fetch(validApi + '/names/' + name); + const nameData = await response.json(); + + if (!nameData?.isForSale) + throw new Error( + i18n.t('question:message.error.name_not_for_sale', { + postProcess: 'capitalizeFirstChar', + }) + ); + + const sellerAddress = nameData.owner; + const sellPrice = +nameData.salePrice; + + const fee = await getFee('BUY_NAME'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.buy_name', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('question:permission.buy_name_detail', { + name: name, + price: sellPrice, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await buyName({ + name, + sellerAddress, + sellPrice, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const signForeignFees = async (data, isFromExtension) => { + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.sign_fee', { + postProcess: 'capitalizeFirstChar', + }), + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const wallet = await getSaveWallet(); + const address = wallet.address0; + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey, + }; + + const unsignedFeesUrl = await createEndpoint( + `/crosschain/unsignedfees/${address}` + ); + + const unsignedFeesResponse = await fetch(unsignedFeesUrl); + + const unsignedFees = await unsignedFeesResponse.json(); + + const signedFees = []; + + unsignedFees.forEach((unsignedFee) => { + const unsignedDataDecoded = Base58.decode(unsignedFee.data); + + const signature = nacl.sign.detached( + unsignedDataDecoded, + keyPair.privateKey + ); + + const signedFee = { + timestamp: unsignedFee.timestamp, + data: `${Base58.encode(signature)}`, + atAddress: unsignedFee.atAddress, + fee: unsignedFee.fee, + }; + + signedFees.push(signedFee); + }); + + const signedFeesUrl = await createEndpoint(`/crosschain/signedfees`); + + await fetch(signedFeesUrl, { + method: 'POST', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + body: `${JSON.stringify(signedFees)}`, + }); + + return true; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; +export const multiPaymentWithPrivateData = async (data, isFromExtension) => { + const requiredFields = ['payments', 'assetId']; + requiredFields.forEach((field) => { + if (data[field] === undefined || data[field] === null) { + throw new Error( + i18n.t('question:message.error.missing_fields', { + fields: field, + postProcess: 'capitalizeFirstChar', + }) + ); + } + }); + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const privateKey = parsedData.privateKey; + const userPublicKey = parsedData.publicKey; + const { fee: paymentFee } = await getFee('TRANSFER_ASSET'); + const { fee: arbitraryFee } = await getFee('ARBITRARY'); + + let name = null; + const payments = data.payments; + const assetId = data.assetId; + const pendingTransactions = []; + const pendingAdditionalArbitraryTxs = []; + const additionalArbitraryTxsWithoutPayment = + data?.additionalArbitraryTxsWithoutPayment || []; + let totalAmount = 0; + let fee = 0; + for (const payment of payments) { + const paymentRefId = uid.rnd(); + const requiredFieldsPayment = ['recipient', 'amount']; + + for (const field of requiredFieldsPayment) { + if (!payment[field]) { + throw new Error( + i18n.t('question:message.error.missing_fields', { + fields: field, + postProcess: 'capitalizeFirstChar', + }) + ); + } + } + + const confirmReceiver = await getNameOrAddress(payment.recipient); + if (confirmReceiver.error) { + throw new Error( + i18n.t('question:message.error.invalid_receiver', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const receiverPublicKey = await getPublicKey(confirmReceiver); + + const amount = +payment.amount.toFixed(8); + + pendingTransactions.push({ + type: 'PAYMENT', + recipientAddress: confirmReceiver, + amount: amount, + paymentRefId, + }); + + fee = fee + +paymentFee; + totalAmount = totalAmount + amount; + + if (payment.arbitraryTxs && payment.arbitraryTxs.length > 0) { + for (const arbitraryTx of payment.arbitraryTxs) { + const requiredFieldsArbitraryTx = ['service', 'identifier', 'base64']; + + for (const field of requiredFieldsArbitraryTx) { + if (!arbitraryTx[field]) { + throw new Error( + i18n.t('question:message.error.missing_fields', { + fields: field, + postProcess: 'capitalizeFirstChar', + }) + ); + } + } + + if (!name) { + const getName = await getNameInfo(); + if (!getName) + throw new Error( + i18n.t('question:message.error.registered_name', { + postProcess: 'capitalizeFirstChar', + }) + ); + name = getName; + } + + const isValid = isValidBase64WithDecode(arbitraryTx.base64); + if (!isValid) + throw new Error( + i18n.t('core:message.error.invalid_base64', { + postProcess: 'capitalizeFirstChar', + }) + ); + if (!arbitraryTx?.service?.includes('_PRIVATE')) + throw new Error( + i18n.t('question:message.generic.private_service', { + postProcess: 'capitalizeFirstChar', + }) + ); + const additionalPublicKeys = arbitraryTx?.additionalPublicKeys || []; + pendingTransactions.push({ + type: 'ARBITRARY', + identifier: arbitraryTx.identifier, + service: arbitraryTx.service, + base64: arbitraryTx.base64, + description: arbitraryTx?.description || '', + paymentRefId, + publicKeys: [receiverPublicKey, ...additionalPublicKeys], + }); + + fee = fee + +arbitraryFee; + } + } + } + + if ( + additionalArbitraryTxsWithoutPayment && + additionalArbitraryTxsWithoutPayment.length > 0 + ) { + for (const arbitraryTx of additionalArbitraryTxsWithoutPayment) { + const requiredFieldsArbitraryTx = ['service', 'identifier', 'base64']; + + for (const field of requiredFieldsArbitraryTx) { + if (!arbitraryTx[field]) { + throw new Error( + i18n.t('question:message.error.missing_fields', { + fields: field, + postProcess: 'capitalizeFirstChar', + }) + ); + } + } + + if (!name) { + const getName = await getNameInfo(); + if (!getName) + throw new Error( + i18n.t('question:message.error.registered_name', { + postProcess: 'capitalizeFirstChar', + }) + ); + name = getName; + } + + const isValid = isValidBase64WithDecode(arbitraryTx.base64); + if (!isValid) + throw new Error( + i18n.t('core:message.error.invalid_base64', { + postProcess: 'capitalizeFirstChar', + }) + ); + if (!arbitraryTx?.service?.includes('_PRIVATE')) + throw new Error( + i18n.t('question:message.generic.private_service', { + postProcess: 'capitalizeFirstChar', + }) + ); + const additionalPublicKeys = arbitraryTx?.additionalPublicKeys || []; + pendingAdditionalArbitraryTxs.push({ + type: 'ARBITRARY', + identifier: arbitraryTx.identifier, + service: arbitraryTx.service, + base64: arbitraryTx.base64, + description: arbitraryTx?.description || '', + publicKeys: additionalPublicKeys, + }); + + fee = fee + +arbitraryFee; + } + } + + if (!name) + throw new Error( + i18n.t('question:message.error.registered_name', { + postProcess: 'capitalizeFirstChar', + }) + ); + const balance = await getBalanceInfo(); + + if (+balance < fee) + throw new Error( + i18n.t('question:message.error.insufficient_balance_qort', { + postProcess: 'capitalizeFirstChar', + }) + ); + const assetBalance = await getAssetBalanceInfo(assetId); + const assetInfo = await getAssetInfo(assetId); + if (assetBalance < totalAmount) + throw new Error( + i18n.t('question:message.error.insufficient_balance', { + postProcess: 'capitalizeFirstChar', + }) + ); + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.pay_publish', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:assets_used_pay', { + asset: assetInfo.name, + postProcess: 'capitalizeFirstChar', + }), + html: ` +
+ + + ${pendingTransactions + .filter((item) => item.type === 'PAYMENT') + .map( + (payment) => ` +
+
Recipient: ${ + payment.recipientAddress + }
+
Amount: ${payment.amount}
+
` + ) + .join('')} + ${[...pendingTransactions, ...pendingAdditionalArbitraryTxs] + .filter((item) => item.type === 'ARBITRARY') + .map( + (arbitraryTx) => ` +
+
Service: ${ + arbitraryTx.service + }
+
Name: ${name}
+
Identifier: ${ + arbitraryTx.identifier + }
+
` + ) + .join('')} +
+ + `, + highlightedText: `Total Amount: ${totalAmount}`, + fee: fee, + }, + isFromExtension + ); + + const { accepted, checkbox1 = false } = resPermission; + if (!accepted) { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + // const failedTxs = [] + const paymentsDone = {}; + + const transactionsDone = []; + + for (const transaction of pendingTransactions) { + const type = transaction.type; + + if (type === 'PAYMENT') { + const makePayment = await retryTransaction( + transferAsset, + [ + { + amount: transaction.amount, + assetId, + recipient: transaction.recipientAddress, + }, + ], + true + ); + if (makePayment) { + transactionsDone.push(makePayment?.signature); + if (transaction.paymentRefId) { + paymentsDone[transaction.paymentRefId] = makePayment; + } + } + } else if (type === 'ARBITRARY' && paymentsDone[transaction.paymentRefId]) { + const objectToEncrypt = { + data: transaction.base64, + payment: paymentsDone[transaction.paymentRefId], + }; + + const toBase64 = await retryTransaction( + objectToBase64, + [objectToEncrypt], + true + ); + + if (!toBase64) continue; // Skip if encryption fails + + const encryptDataResponse = await retryTransaction( + encryptDataGroup, + [ + { + data64: toBase64, + publicKeys: transaction.publicKeys, + privateKey, + userPublicKey, + }, + ], + true + ); + + if (!encryptDataResponse) continue; // Skip if encryption fails + + const resPublish = await retryTransaction( + publishData, + [ + { + registeredName: encodeURIComponent(name), + file: encryptDataResponse, + service: transaction.service, + identifier: encodeURIComponent(transaction.identifier), + uploadType: 'file', + description: transaction?.description, + isBase64: true, + apiVersion: 2, + withFee: true, + }, + ], + true + ); + + if (resPublish?.signature) { + transactionsDone.push(resPublish?.signature); + } + } + } + + for (const transaction of pendingAdditionalArbitraryTxs) { + const objectToEncrypt = { + data: transaction.base64, + }; + + const toBase64 = await retryTransaction( + objectToBase64, + [objectToEncrypt], + true + ); + + if (!toBase64) continue; // Skip if encryption fails + + const encryptDataResponse = await retryTransaction( + encryptDataGroup, + [ + { + data64: toBase64, + publicKeys: transaction.publicKeys, + privateKey, + userPublicKey, + }, + ], + true + ); + + if (!encryptDataResponse) continue; // Skip if encryption fails + + const resPublish = await retryTransaction( + publishData, + [ + { + registeredName: encodeURIComponent(name), + file: encryptDataResponse, + service: transaction.service, + identifier: encodeURIComponent(transaction.identifier), + uploadType: 'file', + description: transaction?.description, + isBase64: true, + apiVersion: 2, + withFee: true, + }, + ], + true + ); + + if (resPublish?.signature) { + transactionsDone.push(resPublish?.signature); + } + } + + return transactionsDone; +}; + +export const transferAssetRequest = async (data, isFromExtension) => { + const requiredFields = ['amount', 'assetId', 'recipient']; + requiredFields.forEach((field) => { + if (data[field] === undefined || data[field] === null) { + throw new Error( + i18n.t('question:message.error.missing_fields', { + fields: field, + postProcess: 'capitalizeFirstChar', + }) + ); + } + }); + const amount = data.amount; + const assetId = data.assetId; + const recipient = data.recipient; + + const { fee } = await getFee('TRANSFER_ASSET'); + const balance = await getBalanceInfo(); + + if (+balance < +fee) + throw new Error( + i18n.t('question:message.error.insufficient_balance_qort', { + postProcess: 'capitalizeFirstChar', + }) + ); + const assetBalance = await getAssetBalanceInfo(assetId); + if (assetBalance < amount) + throw new Error( + i18n.t('question:message.error.insufficient_balance', { + postProcess: 'capitalizeFirstChar', + }) + ); + const confirmReceiver = await getNameOrAddress(recipient); + if (confirmReceiver.error) { + throw new Error( + i18n.t('question:message.error.invalid_receiver', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const assetInfo = await getAssetInfo(assetId); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.transfer_asset', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:asset_name', { + asset: assetInfo?.name, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('question:amount_qty', { + quantity: amount, + postProcess: 'capitalizeFirstChar', + }), + fee: fee, + }, + isFromExtension + ); + + const { accepted } = resPermission; + if (!accepted) { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const res = await transferAsset({ + amount, + recipient: confirmReceiver, + assetId, + }); + return res; +}; diff --git a/src/qortalRequests/qortalRequests.ts b/src/qortal/qortal-requests.ts similarity index 100% rename from src/qortalRequests/qortalRequests.ts rename to src/qortal/qortal-requests.ts diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts index 1878d3f..47d2fe1 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -39,16 +39,16 @@ import { transferAsset, } from '../background/background.ts'; import { getNameInfo, uint8ArrayToObject } from '../encryption/encryption.ts'; -import { showSaveFilePicker } from '../hooks/useQortalMessageListener'; -import { getPublishesFromAdminsAdminSpace } from '../components/Chat/AdminSpaceInner'; -import { extractComponents } from '../components/Chat/MessageDisplay'; +import { showSaveFilePicker } from '../hooks/useQortalMessageListener.tsx'; +import { getPublishesFromAdminsAdminSpace } from '../components/Chat/AdminSpaceInner.tsx'; +import { extractComponents } from '../components/Chat/MessageDisplay.tsx'; import { decryptResource, getGroupAdmins, getPublishesFromAdmins, validateSecretKey, -} from '../components/Group/Group'; -import { QORT_DECIMALS } from '../constants/constants'; +} from '../components/Group/Group.tsx'; +import { QORT_DECIMALS } from '../constants/constants.ts'; import Base58 from '../encryption/Base58.ts'; import ed2curve from '../encryption/ed2curve.ts'; import nacl from '../encryption/nacl-fast.ts'; @@ -64,24 +64,24 @@ import { objectToBase64, uint8ArrayStartsWith, uint8ArrayToBase64, -} from '../qdn/encryption/group-encryption'; +} from '../qdn/encryption/group-encryption.ts'; import { publishData } from '../qdn/publish/publish.ts'; import { getPermission, isRunningGateway, setPermission, } from './qortalRequests.ts'; -import TradeBotCreateRequest from '../transactions/TradeBotCreateRequest'; -import DeleteTradeOffer from '../transactions/TradeBotDeleteRequest'; -import signTradeBotTransaction from '../transactions/signTradeBotTransaction'; -import { createTransaction } from '../transactions/transactions'; -import { executeEvent } from '../utils/events'; -import { fileToBase64 } from '../utils/fileReading'; -import { mimeToExtensionMap } from '../utils/memeTypes'; -import { RequestQueueWithPromise } from '../utils/queue/queue'; -import utils from '../utils/utils'; +import TradeBotCreateRequest from '../transactions/TradeBotCreateRequest.ts'; +import DeleteTradeOffer from '../transactions/TradeBotDeleteRequest.ts'; +import signTradeBotTransaction from '../transactions/signTradeBotTransaction.ts'; +import { createTransaction } from '../transactions/transactions.ts'; +import { executeEvent } from '../utils/events.ts'; +import { fileToBase64 } from '../utils/fileReading/index.ts'; +import { mimeToExtensionMap } from '../utils/memeTypes.ts'; +import { RequestQueueWithPromise } from '../utils/queue/queue.ts'; +import utils from '../utils/utils.ts'; import ShortUniqueId from 'short-unique-id'; -import { isValidBase64WithDecode } from '../utils/decode'; +import { isValidBase64WithDecode } from '../utils/decode.ts'; import i18n from 'i18next'; const uid = new ShortUniqueId({ length: 6 }); From 5546ffdd7a4ca4c778b3075e10182af60d8d2137 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 12:12:25 +0200 Subject: [PATCH 08/12] Remove CustomSvg (unused) --- src/common/CustomSvg.tsx | 14 -------------- src/components/Embeds/AttachmentEmbed.tsx | 3 +-- src/components/Group/Forum/Thread.tsx | 1 + 3 files changed, 2 insertions(+), 16 deletions(-) delete mode 100644 src/common/CustomSvg.tsx diff --git a/src/common/CustomSvg.tsx b/src/common/CustomSvg.tsx deleted file mode 100644 index fc805a9..0000000 --- a/src/common/CustomSvg.tsx +++ /dev/null @@ -1,14 +0,0 @@ -export const CustomSvg = ({ src, color = 'black', size = 24 }) => { - return ( - - {src} - - ); -}; diff --git a/src/components/Embeds/AttachmentEmbed.tsx b/src/components/Embeds/AttachmentEmbed.tsx index d7458a7..adbcd82 100644 --- a/src/components/Embeds/AttachmentEmbed.tsx +++ b/src/components/Embeds/AttachmentEmbed.tsx @@ -226,8 +226,7 @@ export const AttachmentCard = ({ width: '100%', }} > - {' '} - {' '} + )} {errorMsg && ( diff --git a/src/components/Group/Forum/Thread.tsx b/src/components/Group/Forum/Thread.tsx index bf8682f..a293d5f 100644 --- a/src/components/Group/Forum/Thread.tsx +++ b/src/components/Group/Forum/Thread.tsx @@ -931,6 +931,7 @@ export const Thread = ({ }} > + Date: Sat, 24 May 2025 12:14:06 +0200 Subject: [PATCH 09/12] List all namespaces in useTranslation --- src/App.tsx | 8 +++++++- src/Wallets.tsx | 16 ++++++++++++++-- src/components/Apps/AppInfo.tsx | 8 +++++++- src/components/Apps/AppInfoSnippet.tsx | 8 +++++++- src/components/Apps/AppPublish.tsx | 8 +++++++- src/components/Apps/AppRating.tsx | 8 +++++++- src/components/Apps/AppsCategoryDesktop.tsx | 8 +++++++- src/components/Apps/AppsDesktop.tsx | 8 +++++++- src/components/Apps/AppsDevMode.tsx | 8 +++++++- src/components/Apps/AppsDevModeHome.tsx | 8 +++++++- src/components/Apps/AppsHomeDesktop.tsx | 8 +++++++- src/components/Apps/AppsLibraryDesktop.tsx | 8 +++++++- src/components/Apps/AppsNavBarDesktop.tsx | 8 +++++++- src/components/Apps/AppsPrivate.tsx | 8 +++++++- src/components/BuyQortInformation.tsx | 8 +++++++- src/components/Chat/AdminSpace.tsx | 8 +++++++- src/components/Chat/AdminSpaceInner.tsx | 8 +++++++- src/components/Chat/AnnouncementDiscussion.tsx | 8 +++++++- src/components/Chat/AnnouncementItem.tsx | 8 +++++++- src/components/Chat/AnnouncementList.tsx | 8 +++++++- src/components/Chat/ChatDirect.tsx | 8 +++++++- src/components/Chat/ChatGroup.tsx | 8 +++++++- src/components/Chat/ChatList.tsx | 8 +++++++- src/components/Chat/ChatOptions.tsx | 8 +++++++- src/components/Chat/CreateCommonSecret.tsx | 8 +++++++- src/components/Chat/GroupAnnouncements.tsx | 8 +++++++- src/components/Chat/GroupAvatar.tsx | 16 ++++++++++++++-- src/components/Chat/MentionList.tsx | 8 +++++++- src/components/Chat/MessageItem.tsx | 16 ++++++++++++++-- src/components/Chat/TipTap.tsx | 8 +++++++- src/components/CoreSyncStatus.tsx | 8 +++++++- src/components/Desktop/DesktopFooter.tsx | 8 +++++++- src/components/Desktop/DesktopHeader.tsx | 8 +++++++- src/components/Desktop/DesktopSideBar.tsx | 8 +++++++- src/components/Embeds/AttachmentEmbed.tsx | 8 +++++++- src/components/Embeds/Embed.tsx | 8 +++++++- src/components/Embeds/ImageEmbed.tsx | 8 +++++++- src/components/Embeds/PollEmbed.tsx | 16 ++++++++++++++-- src/components/GeneralNotifications.tsx | 8 +++++++- src/components/GlobalActions/JoinGroup.tsx | 8 +++++++- src/components/Group/AddGroup.tsx | 8 +++++++- src/components/Group/AddGroupList.tsx | 8 +++++++- src/components/Group/BlockedUsersModal.tsx | 8 +++++++- src/components/Group/Forum/GroupMail.tsx | 8 +++++++- src/components/Group/Forum/NewThread.tsx | 8 +++++++- src/components/Group/Forum/Thread.tsx | 8 +++++++- src/components/Group/Group.tsx | 8 +++++++- src/components/Group/GroupInvites.tsx | 8 +++++++- src/components/Group/GroupJoinRequests.tsx | 8 +++++++- src/components/Group/GroupList.tsx | 8 +++++++- src/components/Group/HomeDesktop.tsx | 8 +++++++- src/components/Group/InviteMember.tsx | 8 +++++++- src/components/Group/ListOfBans.tsx | 8 +++++++- src/components/Group/ListOfGroupPromotions.tsx | 8 +++++++- src/components/Group/ListOfInvites.tsx | 8 +++++++- src/components/Group/ListOfJoinRequests.tsx | 8 +++++++- src/components/Group/ListOfMembers.tsx | 8 +++++++- .../Group/ListOfThreadPostsWatched.tsx | 8 +++++++- src/components/Group/ManageMembers.tsx | 8 +++++++- src/components/Group/QMailMessages.tsx | 8 +++++++- src/components/Group/Settings.tsx | 16 ++++++++++++++-- src/components/Group/UserListOfInvites.tsx | 8 +++++++- src/components/Group/WalletsAppWrapper.tsx | 8 +++++++- src/components/MainAvatar.tsx | 16 ++++++++++++++-- src/components/Minting/Minting.tsx | 8 +++++++- src/components/NewUsersCTA.tsx | 8 +++++++- src/components/QMailStatus.tsx | 8 +++++++- src/components/QortPayment.tsx | 8 +++++++- src/components/RegisterName.tsx | 8 +++++++- src/components/Save/Save.tsx | 8 +++++++- src/components/Theme/ThemeManager.tsx | 8 +++++++- src/components/Theme/ThemeSelector.tsx | 8 +++++++- src/components/UserLookup.tsx/UserLookup.tsx | 8 +++++++- src/components/WrapperUserAction.tsx | 16 ++++++++++++++-- src/hooks/useHandlePrivateApps.tsx | 8 +++++++- 75 files changed, 574 insertions(+), 82 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 4fd9057..f58c7c2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -308,7 +308,13 @@ function App() { const [isLoading, setIsLoading] = useState(false); const [isLoadingSendCoin, setIsLoadingSendCoin] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const theme = useTheme(); const [ diff --git a/src/Wallets.tsx b/src/Wallets.tsx index e6e4906..c0aa098 100644 --- a/src/Wallets.tsx +++ b/src/Wallets.tsx @@ -53,7 +53,13 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => { const [isOpenSeedModal, setIsOpenSeedModal] = useState(false); const [isLoadingEncryptSeed, setIsLoadingEncryptSeed] = useState(false); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const { isShow, onCancel, onOk, show } = useModal(); const { getRootProps, getInputProps } = useDropzone({ @@ -461,7 +467,13 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => { const [note, setNote] = useState(''); const [isEdit, setIsEdit] = useState(false); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); useEffect(() => { if (wallet?.name) { diff --git a/src/components/Apps/AppInfo.tsx b/src/components/Apps/AppInfo.tsx index 1c77d25..180dac4 100644 --- a/src/components/Apps/AppInfo.tsx +++ b/src/components/Apps/AppInfo.tsx @@ -37,7 +37,13 @@ export const AppInfo = ({ app, myName }) => { ); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const isSelectedAppPinned = !!sortablePinnedApps?.find( (item) => item?.name === app?.name && item?.service === app?.service diff --git a/src/components/Apps/AppInfoSnippet.tsx b/src/components/Apps/AppInfoSnippet.tsx index a0b8f72..41391dc 100644 --- a/src/components/Apps/AppInfoSnippet.tsx +++ b/src/components/Apps/AppInfoSnippet.tsx @@ -42,7 +42,13 @@ export const AppInfoSnippet = ({ ); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); return ( { const [file, setFile] = useState(null); const { show } = useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [tag1, setTag1] = useState(''); const [tag2, setTag2] = useState(''); const [tag3, setTag3] = useState(''); diff --git a/src/components/Apps/AppRating.tsx b/src/components/Apps/AppRating.tsx index edd628a..b0f8258 100644 --- a/src/components/Apps/AppRating.tsx +++ b/src/components/Apps/AppRating.tsx @@ -20,7 +20,13 @@ export const AppRating = ({ app, myName, ratingCountPosition = 'right' }) => { const [openSnack, setOpenSnack] = useState(false); const [infoSnack, setInfoSnack] = useState(null); const hasCalledRef = useRef(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const getRating = useCallback(async (name, service) => { try { diff --git a/src/components/Apps/AppsCategoryDesktop.tsx b/src/components/Apps/AppsCategoryDesktop.tsx index 4c4a541..3752353 100644 --- a/src/components/Apps/AppsCategoryDesktop.tsx +++ b/src/components/Apps/AppsCategoryDesktop.tsx @@ -45,7 +45,13 @@ export const AppsCategoryDesktop = ({ const [searchValue, setSearchValue] = useState(''); const virtuosoRef = useRef(null); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const categoryList = useMemo(() => { if (category?.id === 'all') return availableQapps; diff --git a/src/components/Apps/AppsDesktop.tsx b/src/components/Apps/AppsDesktop.tsx index 0ef5366..27323fd 100644 --- a/src/components/Apps/AppsDesktop.tsx +++ b/src/components/Apps/AppsDesktop.tsx @@ -50,7 +50,13 @@ export const AppsDesktop = ({ const [isEnabledDevMode, setIsEnabledDevMode] = useAtom(enabledDevModeAtom); const { showTutorial } = useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const myApp = useMemo(() => { return availableQapps.find( diff --git a/src/components/Apps/AppsDevMode.tsx b/src/components/Apps/AppsDevMode.tsx index 89358f5..7d90e0b 100644 --- a/src/components/Apps/AppsDevMode.tsx +++ b/src/components/Apps/AppsDevMode.tsx @@ -47,7 +47,13 @@ export const AppsDevMode = ({ const [categories, setCategories] = useState([]); const iframeRefs = useRef({}); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); useEffect(() => { setTimeout(() => { diff --git a/src/components/Apps/AppsDevModeHome.tsx b/src/components/Apps/AppsDevModeHome.tsx index f9ade5d..88218ca 100644 --- a/src/components/Apps/AppsDevModeHome.tsx +++ b/src/components/Apps/AppsDevModeHome.tsx @@ -41,7 +41,13 @@ export const AppsDevModeHome = ({ const [domain, setDomain] = useState('127.0.0.1'); const [port, setPort] = useState(''); const [selectedPreviewFile, setSelectedPreviewFile] = useState(null); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const { isShow, onCancel, onOk, show, message } = useModal(); const { openSnackGlobal, diff --git a/src/components/Apps/AppsHomeDesktop.tsx b/src/components/Apps/AppsHomeDesktop.tsx index b1378f6..ffce539 100644 --- a/src/components/Apps/AppsHomeDesktop.tsx +++ b/src/components/Apps/AppsHomeDesktop.tsx @@ -27,7 +27,13 @@ export const AppsHomeDesktop = ({ }) => { const [qortalUrl, setQortalUrl] = useState(''); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const openQortalUrl = () => { try { diff --git a/src/components/Apps/AppsLibraryDesktop.tsx b/src/components/Apps/AppsLibraryDesktop.tsx index d953bff..d844dbf 100644 --- a/src/components/Apps/AppsLibraryDesktop.tsx +++ b/src/components/Apps/AppsLibraryDesktop.tsx @@ -105,7 +105,13 @@ export const AppsLibraryDesktop = ({ const [searchValue, setSearchValue] = useState(''); const virtuosoRef = useRef(null); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const officialApps = useMemo(() => { return availableQapps.filter( diff --git a/src/components/Apps/AppsNavBarDesktop.tsx b/src/components/Apps/AppsNavBarDesktop.tsx index e6d2ea2..616c09f 100644 --- a/src/components/Apps/AppsNavBarDesktop.tsx +++ b/src/components/Apps/AppsNavBarDesktop.tsx @@ -76,7 +76,13 @@ export const AppsNavBarDesktop = ({ disableBack }) => { ); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [isNewTabWindow, setIsNewTabWindow] = useState(false); const tabsRef = useRef(null); const [anchorEl, setAnchorEl] = useState(null); diff --git a/src/components/Apps/AppsPrivate.tsx b/src/components/Apps/AppsPrivate.tsx index afbf186..355d868 100644 --- a/src/components/Apps/AppsPrivate.tsx +++ b/src/components/Apps/AppsPrivate.tsx @@ -63,7 +63,13 @@ export const AppsPrivate = ({ myName }) => { const [memberGroups] = useAtom(memberGroupsAtom); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const myGroupsPrivate = useMemo(() => { return memberGroups?.filter( diff --git a/src/components/BuyQortInformation.tsx b/src/components/BuyQortInformation.tsx index e10f535..c3519fa 100644 --- a/src/components/BuyQortInformation.tsx +++ b/src/components/BuyQortInformation.tsx @@ -27,7 +27,13 @@ import { useTranslation } from 'react-i18next'; export const BuyQortInformation = ({ balance }) => { const [isOpen, setIsOpen] = useState(false); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const openBuyQortInfoFunc = useCallback( (e) => { diff --git a/src/components/Chat/AdminSpace.tsx b/src/components/Chat/AdminSpace.tsx index 276369a..62a8832 100644 --- a/src/components/Chat/AdminSpace.tsx +++ b/src/components/Chat/AdminSpace.tsx @@ -19,7 +19,13 @@ export const AdminSpace = ({ isOwner, }) => { const [isMoved, setIsMoved] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); useEffect(() => { if (hide) { diff --git a/src/components/Chat/AdminSpaceInner.tsx b/src/components/Chat/AdminSpaceInner.tsx index 97e3fd1..7dbb705 100644 --- a/src/components/Chat/AdminSpaceInner.tsx +++ b/src/components/Chat/AdminSpaceInner.tsx @@ -74,7 +74,13 @@ export const AdminSpaceInner = ({ const [isLoadingPublishKey, setIsLoadingPublishKey] = useState(false); const { show, setInfoSnackCustom, setOpenSnackGlobal } = useContext(QORTAL_APP_CONTEXT); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const getAdminGroupSecretKey = useCallback(async () => { try { diff --git a/src/components/Chat/AnnouncementDiscussion.tsx b/src/components/Chat/AnnouncementDiscussion.tsx index e0da144..b248735 100644 --- a/src/components/Chat/AnnouncementDiscussion.tsx +++ b/src/components/Chat/AnnouncementDiscussion.tsx @@ -40,7 +40,13 @@ export const AnnouncementDiscussion = ({ isPrivate, }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [isSending, setIsSending] = useState(false); const [isLoading, setIsLoading] = useState(false); const [isFocusedParent, setIsFocusedParent] = useState(false); diff --git a/src/components/Chat/AnnouncementItem.tsx b/src/components/Chat/AnnouncementItem.tsx index 98ebe11..a20f58d 100644 --- a/src/components/Chat/AnnouncementItem.tsx +++ b/src/components/Chat/AnnouncementItem.tsx @@ -18,7 +18,13 @@ export const AnnouncementItem = ({ myName, }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [commentLength, setCommentLength] = useState(0); const getNumberOfComments = useCallback(async () => { diff --git a/src/components/Chat/AnnouncementList.tsx b/src/components/Chat/AnnouncementList.tsx index ba2892e..eea6c83 100644 --- a/src/components/Chat/AnnouncementList.tsx +++ b/src/components/Chat/AnnouncementList.tsx @@ -20,7 +20,13 @@ export const AnnouncementList = ({ myName, }) => { const [messages, setMessages] = useState(initialMessages); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); useEffect(() => { cache.clearAll(); diff --git a/src/components/Chat/ChatDirect.tsx b/src/components/Chat/ChatDirect.tsx index 36681e9..0af54a6 100644 --- a/src/components/Chat/ChatDirect.tsx +++ b/src/components/Chat/ChatDirect.tsx @@ -42,7 +42,13 @@ export const ChatDirect = ({ setMobileViewModeKeepOpen, }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue(); const [isFocusedParent, setIsFocusedParent] = useState(false); const [onEditMessage, setOnEditMessage] = useState(null); diff --git a/src/components/Chat/ChatGroup.tsx b/src/components/Chat/ChatGroup.tsx index 783fec9..adc5048 100644 --- a/src/components/Chat/ChatGroup.tsx +++ b/src/components/Chat/ChatGroup.tsx @@ -96,7 +96,13 @@ export const ChatGroup = ({ const [, forceUpdate] = useReducer((x) => x + 1, 0); const lastReadTimestamp = useRef(null); const handleUpdateRef = useRef(null); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const getTimestampEnterChat = async (selectedGroup) => { try { diff --git a/src/components/Chat/ChatList.tsx b/src/components/Chat/ChatList.tsx index 96abf7a..1a47778 100644 --- a/src/components/Chat/ChatList.tsx +++ b/src/components/Chat/ChatList.tsx @@ -181,7 +181,13 @@ export const ChatList = ({ }, []); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); return ( { const queryString = admins.map((name) => `name=${name}`).join('&'); diff --git a/src/components/Chat/GroupAnnouncements.tsx b/src/components/Chat/GroupAnnouncements.tsx index 18170cd..7e1e5f9 100644 --- a/src/components/Chat/GroupAnnouncements.tsx +++ b/src/components/Chat/GroupAnnouncements.tsx @@ -153,7 +153,13 @@ export const GroupAnnouncements = ({ editorRef.current = editorInstance; }; const [, forceUpdate] = useReducer((x) => x + 1, 0); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const triggerRerender = () => { forceUpdate(); // Trigger re-render by updating the state diff --git a/src/components/Chat/GroupAvatar.tsx b/src/components/Chat/GroupAvatar.tsx index f62a241..f71e8fe 100644 --- a/src/components/Chat/GroupAvatar.tsx +++ b/src/components/Chat/GroupAvatar.tsx @@ -33,7 +33,13 @@ export const GroupAvatar = ({ const [avatarFile, setAvatarFile] = useState(null); const [tempAvatar, setTempAvatar] = useState(null); const { show } = useContext(QORTAL_APP_CONTEXT); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [anchorEl, setAnchorEl] = useState(null); const [isLoading, setIsLoading] = useState(false); // Handle child element click to open Popover @@ -262,7 +268,13 @@ const PopoverComp = ({ myName, }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); return ( { - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [selectedIndex, setSelectedIndex] = useState(0); diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index a429846..f570ba2 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -170,7 +170,13 @@ export const MessageItem = memo( }, [message?.id]); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); return ( <> @@ -612,7 +618,13 @@ export const MessageItem = memo( export const ReplyPreview = ({ message, isEdit = false }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); return ( { if (editor && setEditorRef) { diff --git a/src/components/CoreSyncStatus.tsx b/src/components/CoreSyncStatus.tsx index 09a50ed..345678f 100644 --- a/src/components/CoreSyncStatus.tsx +++ b/src/components/CoreSyncStatus.tsx @@ -13,7 +13,13 @@ export const CoreSyncStatus = () => { const [coreInfos, setCoreInfos] = useState({}); const [isUsingGateway, setIsUsingGateway] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const theme = useTheme(); useEffect(() => { diff --git a/src/components/Desktop/DesktopFooter.tsx b/src/components/Desktop/DesktopFooter.tsx index ac567ec..3441cf6 100644 --- a/src/components/Desktop/DesktopFooter.tsx +++ b/src/components/Desktop/DesktopFooter.tsx @@ -67,7 +67,13 @@ export const DesktopFooter = ({ }) => { const [isEnabledDevMode, setIsEnabledDevMode] = useAtom(enabledDevModeAtom); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); if (hide) return; return ( diff --git a/src/components/Desktop/DesktopHeader.tsx b/src/components/Desktop/DesktopHeader.tsx index c82eae0..0428462 100644 --- a/src/components/Desktop/DesktopHeader.tsx +++ b/src/components/Desktop/DesktopHeader.tsx @@ -84,7 +84,13 @@ export const DesktopHeader = ({ }) => { const [value, setValue] = useState(0); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); return ( { const { name, service, identifier } = resourceData; diff --git a/src/components/Embeds/Embed.tsx b/src/components/Embeds/Embed.tsx index 6be25f7..0038830 100644 --- a/src/components/Embeds/Embed.tsx +++ b/src/components/Embeds/Embed.tsx @@ -63,7 +63,13 @@ export const Embed = ({ embedLink }) => { const [parsedData, setParsedData] = useState(null); const setBlobs = useSetAtom(blobControllerAtom); const [selectedGroupId] = useAtom(selectedGroupIdAtom); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const resourceData = useMemo(() => { const parsedDataOnTheFly = parseQortalLink(embedLink); if ( diff --git a/src/components/Embeds/ImageEmbed.tsx b/src/components/Embeds/ImageEmbed.tsx index a7b6534..ab0548d 100644 --- a/src/components/Embeds/ImageEmbed.tsx +++ b/src/components/Embeds/ImageEmbed.tsx @@ -30,7 +30,13 @@ export const ImageCard = ({ encryptionType, }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [isOpen, setIsOpen] = useState(true); const [height, setHeight] = useState('400px'); diff --git a/src/components/Embeds/PollEmbed.tsx b/src/components/Embeds/PollEmbed.tsx index 6d7a510..eb3e304 100644 --- a/src/components/Embeds/PollEmbed.tsx +++ b/src/components/Embeds/PollEmbed.tsx @@ -40,7 +40,13 @@ export const PollCard = ({ const { show, userInfo } = useContext(QORTAL_APP_CONTEXT); const [isLoadingSubmit, setIsLoadingSubmit] = useState(false); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const handleVote = async () => { const fee = await getFee('VOTE_ON_POLL'); @@ -379,7 +385,13 @@ const PollResults = ({ votes }) => { ...votes?.voteCounts?.map((option) => option.voteCount) ); const options = votes?.voteCounts; - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); return ( diff --git a/src/components/GeneralNotifications.tsx b/src/components/GeneralNotifications.tsx index 3e23637..53e85a8 100644 --- a/src/components/GeneralNotifications.tsx +++ b/src/components/GeneralNotifications.tsx @@ -32,7 +32,13 @@ export const GeneralNotifications = ({ address }) => { setAnchorEl(event.currentTarget); }; - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const theme = useTheme(); return ( diff --git a/src/components/GlobalActions/JoinGroup.tsx b/src/components/GlobalActions/JoinGroup.tsx index 95e95e6..aca7e1f 100644 --- a/src/components/GlobalActions/JoinGroup.tsx +++ b/src/components/GlobalActions/JoinGroup.tsx @@ -29,7 +29,13 @@ export const JoinGroup = () => { const [isLoadingInfo, setIsLoadingInfo] = useState(false); const [isOpen, setIsOpen] = useState(false); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [isLoadingJoinGroup, setIsLoadingJoinGroup] = useState(false); const handleJoinGroup = async (e) => { diff --git a/src/components/Group/AddGroup.tsx b/src/components/Group/AddGroup.tsx index ec01443..3815d0d 100644 --- a/src/components/Group/AddGroup.tsx +++ b/src/components/Group/AddGroup.tsx @@ -97,7 +97,13 @@ export const AddGroup = ({ address, open, setOpen }) => { setMaxBlock(event.target.value as string); }; - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const theme = useTheme(); const handleCreateGroup = async () => { diff --git a/src/components/Group/AddGroupList.tsx b/src/components/Group/AddGroupList.tsx index 8cad787..db4460c 100644 --- a/src/components/Group/AddGroupList.tsx +++ b/src/components/Group/AddGroupList.tsx @@ -42,7 +42,13 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => { const { show } = useContext(QORTAL_APP_CONTEXT); const [memberGroups] = useAtom(memberGroupsAtom); const setTxList = useSetAtom(txListAtom); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [groups, setGroups] = useState([]); const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open diff --git a/src/components/Group/BlockedUsersModal.tsx b/src/components/Group/BlockedUsersModal.tsx index c8145b3..8cdd358 100644 --- a/src/components/Group/BlockedUsersModal.tsx +++ b/src/components/Group/BlockedUsersModal.tsx @@ -28,7 +28,13 @@ import { useTranslation } from 'react-i18next'; export const BlockedUsersModal = () => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [isOpenBlockedModal, setIsOpenBlockedModal] = useAtom( isOpenBlockedModalAtom ); diff --git a/src/components/Group/Forum/GroupMail.tsx b/src/components/Group/Forum/GroupMail.tsx index afa6e91..ba4b620 100644 --- a/src/components/Group/Forum/GroupMail.tsx +++ b/src/components/Group/Forum/GroupMail.tsx @@ -73,7 +73,13 @@ export const GroupMail = ({ const anchorElInstanceFilter = useRef(null); const [tempPublishedList, setTempPublishedList] = useState([]); const dataPublishes = useRef({}); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const theme = useTheme(); const [isLoading, setIsLoading] = useState(false); const groupIdRef = useRef(null); diff --git a/src/components/Group/Forum/NewThread.tsx b/src/components/Group/Forum/NewThread.tsx index 9749480..f89e2aa 100644 --- a/src/components/Group/Forum/NewThread.tsx +++ b/src/components/Group/Forum/NewThread.tsx @@ -144,7 +144,13 @@ export const NewThread = ({ setPostReply, isPrivate, }: NewMessageProps) => { - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const { show } = useContext(QORTAL_APP_CONTEXT); const [isOpen, setIsOpen] = useState(false); const [value, setValue] = useState(''); diff --git a/src/components/Group/Forum/Thread.tsx b/src/components/Group/Forum/Thread.tsx index a293d5f..762db5d 100644 --- a/src/components/Group/Forum/Thread.tsx +++ b/src/components/Group/Forum/Thread.tsx @@ -115,7 +115,13 @@ export const Thread = ({ const [isLoading, setIsLoading] = useState(true); const [postReply, setPostReply] = useState(null); const [hasLastPage, setHasLastPage] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const theme = useTheme(); // Update: Use a new ref for the scrollable container const threadContainerRef = useRef(null); diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx index 469a86f..67e3690 100644 --- a/src/components/Group/Group.tsx +++ b/src/components/Group/Group.tsx @@ -444,7 +444,13 @@ export const Group = ({ const [isForceShowCreationKeyPopup, setIsForceShowCreationKeyPopup] = useState(false); const groupsOwnerNamesRef = useRef({}); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [groupsProperties, setGroupsProperties] = useAtom(groupsPropertiesAtom); const setGroupsOwnerNames = useSetAtom(groupsOwnerNamesAtom); diff --git a/src/components/Group/GroupInvites.tsx b/src/components/Group/GroupInvites.tsx index 96f375c..8bad077 100644 --- a/src/components/Group/GroupInvites.tsx +++ b/src/components/Group/GroupInvites.tsx @@ -18,7 +18,13 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => { const [groupsWithJoinRequests, setGroupsWithJoinRequests] = useState([]); const [isExpanded, setIsExpanded] = useState(false); const [loading, setLoading] = useState(true); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const theme = useTheme(); const getJoinRequests = async () => { diff --git a/src/components/Group/GroupJoinRequests.tsx b/src/components/Group/GroupJoinRequests.tsx index 8f617a1..1310174 100644 --- a/src/components/Group/GroupJoinRequests.tsx +++ b/src/components/Group/GroupJoinRequests.tsx @@ -28,7 +28,13 @@ export const GroupJoinRequests = ({ setDesktopViewMode, }) => { const [isExpanded, setIsExpanded] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [groupsWithJoinRequests, setGroupsWithJoinRequests] = useState([]); const [loading, setLoading] = useState(true); const [txList] = useAtom(txListAtom); diff --git a/src/components/Group/GroupList.tsx b/src/components/Group/GroupList.tsx index 156d744..e928331 100644 --- a/src/components/Group/GroupList.tsx +++ b/src/components/Group/GroupList.tsx @@ -49,7 +49,13 @@ export const GroupList = ({ myAddress, }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [isRunningPublicNode] = useAtom(isRunningPublicNodeAtom); return ( diff --git a/src/components/Group/HomeDesktop.tsx b/src/components/Group/HomeDesktop.tsx index a5cf85e..f148be1 100644 --- a/src/components/Group/HomeDesktop.tsx +++ b/src/components/Group/HomeDesktop.tsx @@ -31,7 +31,13 @@ export const HomeDesktop = ({ const [checked1, setChecked1] = useState(false); const [checked2, setChecked2] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const theme = useTheme(); useEffect(() => { diff --git a/src/components/Group/InviteMember.tsx b/src/components/Group/InviteMember.tsx index 8a562d0..8ac8d93 100644 --- a/src/components/Group/InviteMember.tsx +++ b/src/components/Group/InviteMember.tsx @@ -10,7 +10,13 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => { const [value, setValue] = useState(''); const [expiryTime, setExpiryTime] = useState('259200'); const [isLoadingInvite, setIsLoadingInvite] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const inviteMember = async () => { try { diff --git a/src/components/Group/ListOfBans.tsx b/src/components/Group/ListOfBans.tsx index 122ce27..fc84cc9 100644 --- a/src/components/Group/ListOfBans.tsx +++ b/src/components/Group/ListOfBans.tsx @@ -56,7 +56,13 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => { const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open const listRef = useRef(null); const [isLoadingUnban, setIsLoadingUnban] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const getInvites = async (groupId) => { try { diff --git a/src/components/Group/ListOfGroupPromotions.tsx b/src/components/Group/ListOfGroupPromotions.tsx index b3bedab..47f6e10 100644 --- a/src/components/Group/ListOfGroupPromotions.tsx +++ b/src/components/Group/ListOfGroupPromotions.tsx @@ -91,7 +91,13 @@ export const ListOfGroupPromotions = () => { const { show } = useContext(QORTAL_APP_CONTEXT); const setTxList = useSetAtom(txListAtom); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const listRef = useRef(null); const rowVirtualizer = useVirtualizer({ count: promotions.length, diff --git a/src/components/Group/ListOfInvites.tsx b/src/components/Group/ListOfInvites.tsx index 037dddc..3758863 100644 --- a/src/components/Group/ListOfInvites.tsx +++ b/src/components/Group/ListOfInvites.tsx @@ -60,7 +60,13 @@ export const ListOfInvites = ({ const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open const [isLoadingCancelInvite, setIsLoadingCancelInvite] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const listRef = useRef(null); const getInvites = async (groupId) => { diff --git a/src/components/Group/ListOfJoinRequests.tsx b/src/components/Group/ListOfJoinRequests.tsx index 5fbfe9e..2a65cfe 100644 --- a/src/components/Group/ListOfJoinRequests.tsx +++ b/src/components/Group/ListOfJoinRequests.tsx @@ -64,7 +64,13 @@ export const ListOfJoinRequests = ({ const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open const listRef = useRef(null); const [isLoadingAccept, setIsLoadingAccept] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const getInvites = async (groupId) => { try { diff --git a/src/components/Group/ListOfMembers.tsx b/src/components/Group/ListOfMembers.tsx index c57e501..1d23b44 100644 --- a/src/components/Group/ListOfMembers.tsx +++ b/src/components/Group/ListOfMembers.tsx @@ -42,7 +42,13 @@ const ListOfMembers = ({ const [isLoadingMakeAdmin, setIsLoadingMakeAdmin] = useState(false); const [isLoadingRemoveAdmin, setIsLoadingRemoveAdmin] = useState(false); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const listRef = useRef(null); const handlePopoverOpen = (event, index) => { diff --git a/src/components/Group/ListOfThreadPostsWatched.tsx b/src/components/Group/ListOfThreadPostsWatched.tsx index 0868a53..8a3c3d2 100644 --- a/src/components/Group/ListOfThreadPostsWatched.tsx +++ b/src/components/Group/ListOfThreadPostsWatched.tsx @@ -14,7 +14,13 @@ import { useTranslation } from 'react-i18next'; export const ListOfThreadPostsWatched = () => { const [posts, setPosts] = useState([]); const [loading, setLoading] = useState(true); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const getPosts = async () => { try { diff --git a/src/components/Group/ManageMembers.tsx b/src/components/Group/ManageMembers.tsx index e87b002..b8c7052 100644 --- a/src/components/Group/ManageMembers.tsx +++ b/src/components/Group/ManageMembers.tsx @@ -71,7 +71,13 @@ export const ManageMembers = ({ setValue(newValue); }; const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const { show } = useContext(QORTAL_APP_CONTEXT); const setTxList = useSetAtom(txListAtom); diff --git a/src/components/Group/QMailMessages.tsx b/src/components/Group/QMailMessages.tsx index f6490e0..93d96cb 100644 --- a/src/components/Group/QMailMessages.tsx +++ b/src/components/Group/QMailMessages.tsx @@ -54,7 +54,13 @@ export const QMailMessages = ({ userName, userAddress }) => { const [loading, setLoading] = useState(true); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const getMails = useCallback(async () => { try { diff --git a/src/components/Group/Settings.tsx b/src/components/Group/Settings.tsx index 78db9bc..ef6d0cb 100644 --- a/src/components/Group/Settings.tsx +++ b/src/components/Group/Settings.tsx @@ -87,7 +87,13 @@ export const Settings = ({ open, setOpen, rawWallet }) => { const [checked, setChecked] = useState(false); const [isEnabledDevMode, setIsEnabledDevMode] = useAtom(enabledDevModeAtom); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const handleChange = (event: ChangeEvent) => { setChecked(event.target.checked); @@ -235,7 +241,13 @@ const ExportPrivateKey = ({ rawWallet }) => { const [isOpen, setIsOpen] = useState(false); const { setOpenSnackGlobal, setInfoSnackCustom } = useContext(QORTAL_APP_CONTEXT); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const exportPrivateKeyFunc = async () => { try { diff --git a/src/components/Group/UserListOfInvites.tsx b/src/components/Group/UserListOfInvites.tsx index 2717af9..50cbb5b 100644 --- a/src/components/Group/UserListOfInvites.tsx +++ b/src/components/Group/UserListOfInvites.tsx @@ -61,7 +61,13 @@ export const UserListOfInvites = ({ const [invites, setInvites] = useState([]); const [isLoading, setIsLoading] = useState(false); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open const listRef = useRef(null); diff --git a/src/components/Group/WalletsAppWrapper.tsx b/src/components/Group/WalletsAppWrapper.tsx index ee9885a..990544d 100644 --- a/src/components/Group/WalletsAppWrapper.tsx +++ b/src/components/Group/WalletsAppWrapper.tsx @@ -15,7 +15,13 @@ import { useAtom } from 'jotai'; import { useTranslation } from 'react-i18next'; export const WalletsAppWrapper = () => { - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const iframeRef = useRef(null); const [isOpen, setIsOpen] = useState(false); const [navigationController, setNavigationController] = useAtom( diff --git a/src/components/MainAvatar.tsx b/src/components/MainAvatar.tsx index 8e03e97..c985bb2 100644 --- a/src/components/MainAvatar.tsx +++ b/src/components/MainAvatar.tsx @@ -45,7 +45,13 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => { const open = Boolean(anchorEl); const id = open ? 'avatar-img' : undefined; - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const checkIfAvatarExists = async () => { try { @@ -257,7 +263,13 @@ const PopoverComp = ({ myName, }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); return ( { const { show: showKey, message } = useModal(); const { isShow: isShowNext, onOk, show: showNext } = useModal(); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [info, setInfo] = useState(null); const [names, setNames] = useState({}); const [accountInfos, setAccountInfos] = useState({}); diff --git a/src/components/NewUsersCTA.tsx b/src/components/NewUsersCTA.tsx index 94606d1..d444717 100644 --- a/src/components/NewUsersCTA.tsx +++ b/src/components/NewUsersCTA.tsx @@ -3,7 +3,13 @@ import { Spacer } from '../common/Spacer'; import { useTranslation } from 'react-i18next'; export const NewUsersCTA = ({ balance }) => { - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); if (balance === undefined || +balance > 0) return null; diff --git a/src/components/QMailStatus.tsx b/src/components/QMailStatus.tsx index 45e51c8..f0b51f3 100644 --- a/src/components/QMailStatus.tsx +++ b/src/components/QMailStatus.tsx @@ -8,7 +8,13 @@ import { useTranslation } from 'react-i18next'; import { useAtom } from 'jotai'; export const QMailStatus = () => { - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const theme = useTheme(); const [lastEnteredTimestamp, setLastEnteredTimestamp] = useAtom( diff --git a/src/components/QortPayment.tsx b/src/components/QortPayment.tsx index 1907686..f31c7c0 100644 --- a/src/components/QortPayment.tsx +++ b/src/components/QortPayment.tsx @@ -7,7 +7,13 @@ import { useTranslation } from 'react-i18next'; export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [paymentTo, setPaymentTo] = useState(defaultPaymentTo); const [paymentAmount, setPaymentAmount] = useState(0); const [paymentPassword, setPaymentPassword] = useState(''); diff --git a/src/components/RegisterName.tsx b/src/components/RegisterName.tsx index 2b5b224..eb988ff 100644 --- a/src/components/RegisterName.tsx +++ b/src/components/RegisterName.tsx @@ -51,7 +51,13 @@ export const RegisterName = ({ ); const [nameFee, setNameFee] = useState(null); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const checkIfNameExisits = async (name) => { if (!name?.trim()) { setIsNameAvailable(Availability.NULL); diff --git a/src/components/Save/Save.tsx b/src/components/Save/Save.tsx index ed5c77e..9a9af08 100644 --- a/src/components/Save/Save.tsx +++ b/src/components/Save/Save.tsx @@ -84,7 +84,13 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { const [anchorEl, setAnchorEl] = useState(null); const { show } = useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const hasChanged = useMemo(() => { const newChanges = { diff --git a/src/components/Theme/ThemeManager.tsx b/src/components/Theme/ThemeManager.tsx index 69c63c9..f6b1551 100644 --- a/src/components/Theme/ThemeManager.tsx +++ b/src/components/Theme/ThemeManager.tsx @@ -82,7 +82,13 @@ export default function ThemeManager() { }); const [currentTab, setCurrentTab] = useState('light'); const nameInputRef = useRef(null); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); useEffect(() => { if (openEditor && nameInputRef.current) { diff --git a/src/components/Theme/ThemeSelector.tsx b/src/components/Theme/ThemeSelector.tsx index dd0dbba..6c25e3c 100644 --- a/src/components/Theme/ThemeSelector.tsx +++ b/src/components/Theme/ThemeSelector.tsx @@ -5,7 +5,13 @@ import DarkModeIcon from '@mui/icons-material/DarkMode'; import { useTranslation } from 'react-i18next'; const ThemeSelector = () => { - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const { themeMode, toggleTheme } = useThemeContext(); return ( diff --git a/src/components/UserLookup.tsx/UserLookup.tsx b/src/components/UserLookup.tsx/UserLookup.tsx index 3be4ef6..bd52b2a 100644 --- a/src/components/UserLookup.tsx/UserLookup.tsx +++ b/src/components/UserLookup.tsx/UserLookup.tsx @@ -49,7 +49,13 @@ function formatAddress(str) { export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [nameOrAddress, setNameOrAddress] = useState(''); const [inputValue, setInputValue] = useState(''); const { results, isLoading } = useNameSearch(inputValue); diff --git a/src/components/WrapperUserAction.tsx b/src/components/WrapperUserAction.tsx index 490333d..caaee97 100644 --- a/src/components/WrapperUserAction.tsx +++ b/src/components/WrapperUserAction.tsx @@ -14,7 +14,13 @@ import { useTranslation } from 'react-i18next'; export const WrapperUserAction = ({ children, address, name, disabled }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [isRunningPublicNode] = useAtom(isRunningPublicNodeAtom); const [anchorEl, setAnchorEl] = useState(null); @@ -177,7 +183,13 @@ const BlockUser = ({ address, name, handleClose }) => { const { isUserBlocked, addToBlockList, removeBlockFromList } = useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); useEffect(() => { if (!address) return; diff --git a/src/hooks/useHandlePrivateApps.tsx b/src/hooks/useHandlePrivateApps.tsx index 3876130..1065502 100644 --- a/src/hooks/useHandlePrivateApps.tsx +++ b/src/hooks/useHandlePrivateApps.tsx @@ -22,7 +22,13 @@ export const useHandlePrivateApps = () => { } = useContext(QORTAL_APP_CONTEXT); const setSortablePinnedApps = useSetAtom(sortablePinnedAppsAtom); const setSettingsLocalLastUpdated = useSetAtom(settingsLocalLastUpdatedAtom); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const openApp = async ( privateAppProperties, From cb86a9b89da89a29b72c09a5fa3efcdaef0aed88 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 12:19:11 +0200 Subject: [PATCH 10/12] Rename files and folders (uniform the case) --- src/App.tsx | 2 +- src/Wallets.tsx | 2 +- src/components/Chat/ChatDirect.tsx | 2 +- src/components/Chat/ChatGroup.tsx | 2 +- src/components/Group/Group.tsx | 2 +- src/main.tsx | 4 +- src/{ => messaging}/MessageQueueContext.tsx | 0 ...ackground.tsx => MessagesToBackground.tsx} | 44 ++++++++++++++----- ...enerator.ts => randomSentenceGenerator.ts} | 0 src/utils/{Size => size}/index.ts | 0 10 files changed, 40 insertions(+), 18 deletions(-) rename src/{ => messaging}/MessageQueueContext.tsx (100%) rename src/messaging/{messagesToBackground.tsx => MessagesToBackground.tsx} (71%) rename src/utils/seedPhrase/{RandomSentenceGenerator.ts => randomSentenceGenerator.ts} (100%) rename src/utils/{Size => size}/index.ts (100%) diff --git a/src/App.tsx b/src/App.tsx index f58c7c2..9e1332b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -35,7 +35,7 @@ import PersonSearchIcon from '@mui/icons-material/PersonSearch'; import qortLogo from './assets/qort.png'; import { Return } from './assets/Icons/Return.tsx'; import WarningIcon from '@mui/icons-material/Warning'; -import './utils/seedPhrase/RandomSentenceGenerator'; +import './utils/seedPhrase/randomSentenceGenerator.ts'; import EngineeringIcon from '@mui/icons-material/Engineering'; import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet'; import PriorityHighIcon from '@mui/icons-material/PriorityHigh'; diff --git a/src/Wallets.tsx b/src/Wallets.tsx index c0aa098..1fe7583 100644 --- a/src/Wallets.tsx +++ b/src/Wallets.tsx @@ -75,7 +75,7 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => { const fileContents = await new Promise((resolve, reject) => { const reader = new FileReader(); - reader.onabort = () => reject('File reading was aborted'); + reader.onabort = () => reject('File reading was aborted'); // TODO translate reader.onerror = () => reject('File reading has failed'); reader.onload = () => { // Resolve the promise with the reader result when reading completes diff --git a/src/components/Chat/ChatDirect.tsx b/src/components/Chat/ChatDirect.tsx index 0af54a6..6f95eee 100644 --- a/src/components/Chat/ChatDirect.tsx +++ b/src/components/Chat/ChatDirect.tsx @@ -15,7 +15,7 @@ import { resumeAllQueues, } from '../../App'; import { getPublicKey } from '../../background/background.ts'; -import { useMessageQueue } from '../../MessageQueueContext'; +import { useMessageQueue } from '../../messaging/MessageQueueContext.tsx'; import { executeEvent, subscribeToEvent, diff --git a/src/components/Chat/ChatGroup.tsx b/src/components/Chat/ChatGroup.tsx index adc5048..3d2f067 100644 --- a/src/components/Chat/ChatGroup.tsx +++ b/src/components/Chat/ChatGroup.tsx @@ -25,7 +25,7 @@ import { } from '../../App'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from '../../constants/constants'; -import { useMessageQueue } from '../../MessageQueueContext'; +import { useMessageQueue } from '../../messaging/MessageQueueContext.tsx'; import { executeEvent, subscribeToEvent, diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx index 67e3690..c64c8cc 100644 --- a/src/components/Group/Group.tsx +++ b/src/components/Group/Group.tsx @@ -43,7 +43,7 @@ import { } from '../../utils/events'; import { RequestQueueWithPromise } from '../../utils/queue/queue'; import { WebSocketActive } from './WebsocketActive'; -import { useMessageQueue } from '../../MessageQueueContext'; +import { useMessageQueue } from '../../messaging/MessageQueueContext'; import { HomeDesktop } from './HomeDesktop'; import { IconWrapper } from '../Desktop/DesktopFooter'; import { DesktopHeader } from '../Desktop/DesktopHeader'; diff --git a/src/main.tsx b/src/main.tsx index d6da0d2..2b224c8 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,8 +1,8 @@ import { createRoot } from 'react-dom/client'; import App from './App.tsx'; import '../src/styles/index.css'; -import './messaging/messagesToBackground'; -import { MessageQueueProvider } from './MessageQueueContext.tsx'; +import './messaging/MessagesToBackground.tsx'; +import { MessageQueueProvider } from './messaging/MessageQueueContext.tsx'; import { ThemeProvider } from './components/Theme/ThemeContext.tsx'; import { CssBaseline } from '@mui/material'; import './i18n/i18n.js'; diff --git a/src/MessageQueueContext.tsx b/src/messaging/MessageQueueContext.tsx similarity index 100% rename from src/MessageQueueContext.tsx rename to src/messaging/MessageQueueContext.tsx diff --git a/src/messaging/messagesToBackground.tsx b/src/messaging/MessagesToBackground.tsx similarity index 71% rename from src/messaging/messagesToBackground.tsx rename to src/messaging/MessagesToBackground.tsx index 4eebc5e..a325352 100644 --- a/src/messaging/messagesToBackground.tsx +++ b/src/messaging/MessagesToBackground.tsx @@ -1,5 +1,3 @@ - - // Utility to generate unique request IDs function generateRequestId() { return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`; @@ -9,34 +7,55 @@ function generateRequestId() { const callbackMap = new Map(); // Global listener for handling message responses -window.addEventListener("message", (event) => { +window.addEventListener('message', (event) => { const { type, requestId, payload, error, message } = event.data; // Only process messages of type `backgroundMessageResponse` - if (type !== "backgroundMessageResponse") return; + if (type !== 'backgroundMessageResponse') return; // Check if there’s a callback stored for this requestId if (callbackMap.has(requestId)) { const { resolve, reject } = callbackMap.get(requestId); callbackMap.delete(requestId); // Remove callback after use - resolve(event.data) + resolve(event.data); } }); -export const sendMessageBackground = (action, data = {}, timeout = 240000, isExtension, appInfo, skipAuth) => { +export const sendMessageBackground = ( + action, + data = {}, + timeout = 240000, + isExtension, + appInfo, + skipAuth +) => { return new Promise((resolve, reject) => { const requestId = generateRequestId(); // Unique ID for each request callbackMap.set(requestId, { resolve, reject }); // Store both resolve and reject callbacks - const targetOrigin = window.location.origin + const targetOrigin = window.location.origin; // Send the message with `backgroundMessage` type - window.postMessage({ type: "backgroundMessage", action, requestId, payload: data, isExtension, appInfo, skipAuth }, targetOrigin); + window.postMessage( + { + type: 'backgroundMessage', + action, + requestId, + payload: data, + isExtension, + appInfo, + skipAuth, + }, + targetOrigin + ); // Set up a timeout to automatically reject if no response is received const timeoutId = setTimeout(() => { // Remove the callback to prevent memory leaks callbackMap.delete(requestId); - reject({ error: "timeout", message: `Request timed out after ${timeout} ms` }); + reject({ + error: 'timeout', + message: `Request timed out after ${timeout} ms`, + }); }, timeout); // Adjust resolve/reject to clear the timeout when a response arrives @@ -48,14 +67,17 @@ export const sendMessageBackground = (action, data = {}, timeout = 240000, isExt reject: (error) => { clearTimeout(timeoutId); // Clear the timeout if an error occurs reject(error); - } + }, }); }).then((response) => { // Return payload or error based on response content if (response?.payload !== null && response?.payload !== undefined) { return response.payload; } else if (response?.error) { - return { error: response.error, message: response?.message || "An error occurred" }; + return { + error: response.error, + message: response?.message || 'An error occurred', + }; } }); }; diff --git a/src/utils/seedPhrase/RandomSentenceGenerator.ts b/src/utils/seedPhrase/randomSentenceGenerator.ts similarity index 100% rename from src/utils/seedPhrase/RandomSentenceGenerator.ts rename to src/utils/seedPhrase/randomSentenceGenerator.ts diff --git a/src/utils/Size/index.ts b/src/utils/size/index.ts similarity index 100% rename from src/utils/Size/index.ts rename to src/utils/size/index.ts From 0209418c1d5eff1736f74bbf46eae973791a2588 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 12:21:37 +0200 Subject: [PATCH 11/12] Move file into components --- src/App.tsx | 2 +- src/{ => components}/Wallets.tsx | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) rename src/{ => components}/Wallets.tsx (96%) diff --git a/src/App.tsx b/src/App.tsx index 9e1332b..ed70e64 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -118,7 +118,7 @@ import { } from './atoms/global'; import { NotAuthenticated } from './components/NotAuthenticated.tsx'; import { handleGetFileFromIndexedDB } from './utils/indexedDB'; -import { Wallets } from './Wallets'; +import { Wallets } from './components/Wallets.tsx'; import { useFetchResources } from './common/useFetchResources'; import { Tutorials } from './components/Tutorials/Tutorials'; import { useHandleTutorials } from './hooks/useHandleTutorials.tsx'; diff --git a/src/Wallets.tsx b/src/components/Wallets.tsx similarity index 96% rename from src/Wallets.tsx rename to src/components/Wallets.tsx index 1fe7583..24aa4705 100644 --- a/src/Wallets.tsx +++ b/src/components/Wallets.tsx @@ -18,24 +18,24 @@ import { Input, useTheme, } from '@mui/material'; -import { CustomButton } from './styles/App-styles'; +import { CustomButton } from '../styles/App-styles.ts'; import { useDropzone } from 'react-dropzone'; import EditIcon from '@mui/icons-material/Edit'; -import { Label } from './components/Group/AddGroup'; -import { Spacer } from './common/Spacer'; +import { Label } from './Group/AddGroup.tsx'; +import { Spacer } from '../common/Spacer.tsx'; import { getWallets, storeWallets, walletVersion, -} from './background/background.ts'; -import { useModal } from './common/useModal'; -import PhraseWallet from './utils/generateWallet/phrase-wallet'; -import { decryptStoredWalletFromSeedPhrase } from './utils/decryptWallet'; -import { crypto } from './constants/decryptWallet'; +} from '../background/background.ts'; +import { useModal } from '../common/useModal.tsx'; +import PhraseWallet from '../utils/generateWallet/phrase-wallet.ts'; +import { decryptStoredWalletFromSeedPhrase } from '../utils/decryptWallet.ts'; +import { crypto } from '../constants/decryptWallet.ts'; import { LoadingButton } from '@mui/lab'; -import { PasswordField } from './components'; -import { HtmlTooltip } from './components/NotAuthenticated'; -import { QORTAL_APP_CONTEXT } from './App'; +import { PasswordField } from './index.ts'; +import { HtmlTooltip } from './NotAuthenticated.tsx'; +import { QORTAL_APP_CONTEXT } from '../App.tsx'; import { useTranslation } from 'react-i18next'; const parsefilenameQortal = (filename) => { From b23ce1a13640bff279d88a8e67ad2afd1603bae9 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 12:26:21 +0200 Subject: [PATCH 12/12] Move hooks into proper folder --- src/App.tsx | 4 ++-- src/components/Apps/AppsDevModeHome.tsx | 2 +- src/components/Group/BlockedUsersModal.tsx | 2 +- src/components/Minting/Minting.tsx | 2 +- src/components/Wallets.tsx | 2 +- src/{common => hooks}/useFetchResources.tsx | 0 src/{common => hooks}/useModal.tsx | 0 7 files changed, 6 insertions(+), 6 deletions(-) rename src/{common => hooks}/useFetchResources.tsx (100%) rename src/{common => hooks}/useModal.tsx (100%) diff --git a/src/App.tsx b/src/App.tsx index ed70e64..4aad404 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -64,7 +64,7 @@ import { Loader } from './components/Loader'; import { PasswordField, ErrorText } from './components'; import { Group, requestQueueMemberNames } from './components/Group/Group'; import { TaskManager } from './components/TaskManager/TaskManager.tsx'; -import { useModal } from './common/useModal'; +import { useModal } from './hooks/useModal.tsx'; import { CustomizedSnackbars } from './components/Snackbar/Snackbar'; import SettingsIcon from '@mui/icons-material/Settings'; import LogoutIcon from '@mui/icons-material/Logout'; @@ -119,7 +119,7 @@ import { import { NotAuthenticated } from './components/NotAuthenticated.tsx'; import { handleGetFileFromIndexedDB } from './utils/indexedDB'; import { Wallets } from './components/Wallets.tsx'; -import { useFetchResources } from './common/useFetchResources'; +import { useFetchResources } from './hooks/useFetchResources.tsx'; import { Tutorials } from './components/Tutorials/Tutorials'; import { useHandleTutorials } from './hooks/useHandleTutorials.tsx'; import { useHandleUserInfo } from './hooks/useHandleUserInfo.tsx'; diff --git a/src/components/Apps/AppsDevModeHome.tsx b/src/components/Apps/AppsDevModeHome.tsx index 88218ca..db32248 100644 --- a/src/components/Apps/AppsDevModeHome.tsx +++ b/src/components/Apps/AppsDevModeHome.tsx @@ -22,7 +22,7 @@ import { Add } from '@mui/icons-material'; import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { executeEvent } from '../../utils/events'; import { Spacer } from '../../common/Spacer'; -import { useModal } from '../../common/useModal'; +import { useModal } from '../../hooks/useModal.tsx'; import { createEndpoint, isUsingLocal } from '../../background/background.ts'; import { Label } from '../Group/AddGroup'; import ShortUniqueId from 'short-unique-id'; diff --git a/src/components/Group/BlockedUsersModal.tsx b/src/components/Group/BlockedUsersModal.tsx index 8cdd358..487711e 100644 --- a/src/components/Group/BlockedUsersModal.tsx +++ b/src/components/Group/BlockedUsersModal.tsx @@ -20,7 +20,7 @@ import { } from '../../utils/events'; import { validateAddress } from '../../utils/validateAddress'; import { getNameInfo, requestQueueMemberNames } from './Group'; -import { useModal } from '../../common/useModal'; +import { useModal } from '../../hooks/useModal'; import { isOpenBlockedModalAtom } from '../../atoms/global'; import InfoIcon from '@mui/icons-material/Info'; import { useAtom } from 'jotai'; diff --git a/src/components/Minting/Minting.tsx b/src/components/Minting/Minting.tsx index ce3846b..cddd67c 100644 --- a/src/components/Minting/Minting.tsx +++ b/src/components/Minting/Minting.tsx @@ -24,7 +24,7 @@ import { import { getFee } from '../../background/background.ts'; import { Spacer } from '../../common/Spacer'; import { FidgetSpinner } from 'react-loader-spinner'; -import { useModal } from '../../common/useModal'; +import { useModal } from '../../hooks/useModal.tsx'; import { useAtom, useSetAtom } from 'jotai'; import { memberGroupsAtom, txListAtom } from '../../atoms/global'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/Wallets.tsx b/src/components/Wallets.tsx index 24aa4705..ae749d3 100644 --- a/src/components/Wallets.tsx +++ b/src/components/Wallets.tsx @@ -28,7 +28,7 @@ import { storeWallets, walletVersion, } from '../background/background.ts'; -import { useModal } from '../common/useModal.tsx'; +import { useModal } from '../hooks/useModal.tsx'; import PhraseWallet from '../utils/generateWallet/phrase-wallet.ts'; import { decryptStoredWalletFromSeedPhrase } from '../utils/decryptWallet.ts'; import { crypto } from '../constants/decryptWallet.ts'; diff --git a/src/common/useFetchResources.tsx b/src/hooks/useFetchResources.tsx similarity index 100% rename from src/common/useFetchResources.tsx rename to src/hooks/useFetchResources.tsx diff --git a/src/common/useModal.tsx b/src/hooks/useModal.tsx similarity index 100% rename from src/common/useModal.tsx rename to src/hooks/useModal.tsx