Merge branch 'convert_to_ts' of https://github.com/jejolare-dev/zano-extension into convert_to_ts

This commit is contained in:
jejolare 2024-12-01 13:48:26 +07:00
commit 389fd4e45f
61 changed files with 2827 additions and 1451 deletions

2020
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -51,17 +51,29 @@
"@babel/core": "^7.21.4",
"@babel/preset-env": "^7.21.4",
"@babel/preset-react": "^7.18.6",
"@types/big.js": "^6.2.2",
"@types/chrome": "^0.0.251",
"babel-loader": "^9.1.2",
"@types/crypto-js": "^4.2.2",
"@types/json-bigint": "^1.0.4",
"@types/node": "^22.10.0",
"@types/node-forge": "^1.3.11",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/sha256": "^0.2.2",
"babel-loader": "^9.2.1",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.3",
"html-webpack-plugin": "^5.5.0",
"css-loader": "^6.11.0",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.6.3",
"sass": "^1.62.0",
"sass-loader": "^13.2.2",
"style-loader": "^3.3.2",
"sass-loader": "^13.3.3",
"style-loader": "^3.3.4",
"ts-loader": "^9.5.1",
"typescript": "^5.7.2",
"url-loader": "^4.1.1",
"webpack": "^5.79.0",
"webpack-cli": "^5.0.1",
"webpack": "^5.96.1",
"webpack-cli": "^5.1.4",
"webpack-merge": "^5.8.0"
}
}

View file

@ -1,4 +1,5 @@
/*global chrome*/
import React from "react";
import { useContext, useEffect, useState, useCallback } from "react";
import { Router, goTo } from "react-chrome-extension-router";
import AppPlug from "./components/AppPlug/AppPlug";
@ -25,14 +26,14 @@ import {
updateConfirmationModal,
updateTransactionStatus,
setConnectData,
setWhiteList
setWhiteList,
} from "./store/actions";
import { Store } from "./store/store-reducer";
import { getZanoPrice } from "./api/coingecko";
import "./styles/App.scss";
import PasswordPage from "./components/PasswordPage/PasswordPage";
import MessageSignPage from "./components/MessageSignPage/MessageSignPage";
import AliasCreatePage from "./components/AliasCreatePage/AliasCreatePage";
import AliasCreatePage from "./components/AliasCreatePage/AliasCreatePaget";
import ConnectPage from "./components/ConnectPage/ConnectPage";
import ConnectKeyUtils from "./utils/ConnectKeyUtils";
import { defaultPort } from "./config/config";
@ -42,10 +43,40 @@ import Big from "big.js";
import swapModalStyles from "./styles/SwapModal.module.scss";
import useGetAsset from "./hooks/useGetAsset";
// Types
type dispatchType = () => void;
type transferType = { transfer: { sender: string, destination: string, amount: string, asset: { ticker: string } }, id: number };
type RequestType = { method: string; assetId: string, amount: string, destinationAddress: string, destinationChainId: string };
type SwapRequest = {
id: string;
swap: {
destinationAddress: string;
destinationAsset: string;
destinationAssetAmount: string;
currentAsset: string;
currentAssetAmount: string;
};
};
type SwapProposal = {
to_finalizer: { amount: Big }[];
to_initiator: { amount: Big }[];
};
type Asset = {
decimal_point: number;
[key: string]: any;
};
type AcceptSwapReq = {
id: string;
hex_raw_proposal: string;
swapProposal: SwapProposal;
receivingAsset: Asset;
sendingAsset: Asset;
};
function App() {
const { state, dispatch } = useContext(Store);
const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
const {getAssetById} = useGetAsset();
const { getAssetById } = useGetAsset();
const [incorrectPassword, setIncorrectPassword] = useState(false);
const [loggedIn, setLoggedIn] = useState(false);
@ -61,14 +92,14 @@ function App() {
useEffect(() => {
async function loadLogin() {
const password = (await getSessionPassword());
const password = await getSessionPassword();
setLoggedIn(!!password);
if (password) {
const connectData = ConnectKeyUtils.getConnectData(password);
setConnectData(dispatch, {
token: connectData.token,
port: connectData.port,
setConnectData(dispatch as dispatchType, {
token: String(connectData?.token),
port: String(connectData?.port),
});
}
}
@ -92,7 +123,7 @@ function App() {
const closeModal = () => {
setConfirmationModalOpen(false);
updateConfirmationModal(dispatch, null);
updateConfirmationModal(dispatch as dispatchType, null);
chrome.storage?.local?.remove?.(["pendingTx"]);
chrome.action.setBadgeText({ text: "" });
};
@ -105,7 +136,7 @@ function App() {
} else {
closeModal();
console.log(response.status);
updateTransactionStatus(dispatch, {
updateTransactionStatus(dispatch as dispatchType, {
visible: true,
type: "error",
code: response.status.code || 0,
@ -128,8 +159,8 @@ function App() {
const walletActive = await fetchBackground({
method: "GET_WALLET_DATA",
});
updateWalletConnected(dispatch, !walletActive.error);
updateLoading(dispatch, false);
updateWalletConnected(dispatch as dispatchType, !walletActive.error);
updateLoading(dispatch as dispatchType, false);
};
const getWalletData = async () => {
@ -137,7 +168,7 @@ function App() {
const walletsList = await fetchBackground({ method: "GET_WALLETS" });
if (!walletsList.data) return;
updateWalletsList(dispatch, walletsList.data);
updateWalletsList(dispatch as dispatchType, walletsList.data);
const walletData = await fetchBackground({
method: "GET_WALLET_DATA",
@ -148,7 +179,7 @@ function App() {
// console.log("WALLET DATA:");
// console.log(walletData.data);
updateWalletData(dispatch, {
updateWalletData(dispatch as dispatchType, {
address,
alias,
balance,
@ -157,7 +188,7 @@ function App() {
});
console.log("wallet data updated");
updateLoading(dispatch, false);
updateLoading(dispatch as dispatchType, false);
setFirstWalletLoaded(true);
};
@ -176,7 +207,7 @@ function App() {
async function updateWhiteList() {
const whiteList = await fetchBackground({ method: "GET_WHITELIST" });
if (whiteList.data) {
setWhiteList(dispatch, whiteList.data);
setWhiteList(dispatch as dispatchType, whiteList.data);
}
}
updateWhiteList();
@ -185,12 +216,12 @@ function App() {
useEffect(() => {
getZanoPrice().then((priceData) => {
console.log("price data", priceData);
updatePriceData(dispatch, priceData);
updatePriceData(dispatch as dispatchType, priceData);
});
}, [dispatch]);
useEffect(() => {
const listener = (request, sender, sendResponse) => {
const listener = (request: RequestType, sender: chrome.runtime.MessageSender, sendResponse: (response: { status: string }) => void) => {
if (
!(
"assetId" in request &&
@ -204,7 +235,7 @@ function App() {
}
if (request.method === "BRIDGING_TRANSFER") {
updateConfirmationModal(dispatch, {
updateConfirmationModal(dispatch as dispatchType, {
method: "SEND_TRANSFER",
params: [
request.assetId,
@ -234,7 +265,7 @@ function App() {
useEffect(() => {
chrome.storage?.local?.get?.(["pendingTx"], function (result) {
if (result.pendingTx) {
updateConfirmationModal(dispatch, {
updateConfirmationModal(dispatch as dispatchType, {
method: "SEND_TRANSFER",
params: [
result.pendingTx.assetId,
@ -262,22 +293,23 @@ function App() {
method: "SET_ACTIVE_WALLET",
id: walletId,
});
updateActiveWalletId(dispatch, walletId);
updateActiveWalletId(dispatch as dispatchType, walletId);
});
}, [dispatch]);
const appConnected = !!(state.connectCredentials?.token || ConnectKeyUtils.getConnectKeyEncrypted());
const appConnected = !!(
state.connectCredentials?.token || ConnectKeyUtils.getConnectKeyEncrypted()
);
useEffect(() => {
async function modalLoad() {
async function getTransferRequests() {
const transferRes = await fetchBackground({ method: "GET_TRANSFER_REQUEST" });
const transferRes = await fetchBackground({
method: "GET_TRANSFER_REQUEST",
});
const transferRequests = transferRes.data;
const tranfserPageReqs = transferRequests.map((e) => {
const {transfer} = e;
const tranfserPageReqs = transferRequests.map((e: transferType) => {
const { transfer } = e;
return {
id: e.id,
method: "FINALIZE_TRANSFER_REQUEST",
@ -285,24 +317,27 @@ function App() {
params: [
{
key: "From",
value: transfer.sender ? Formatters.walletAddress(transfer.sender) : "???"
value: transfer.sender
? Formatters.walletAddress(transfer.sender)
: "???",
},
{
key: "To",
value: transfer.destination ? Formatters.walletAddress(transfer.destination) : "???"
value: transfer.destination
? Formatters.walletAddress(transfer.destination)
: "???",
},
{
key: "Amount",
value: transfer.amount || "???"
value: transfer.amount || "???",
},
{
key: "Asset",
value: transfer?.asset?.ticker || "???"
value: transfer?.asset?.ticker || "???",
},
]
}
})
],
};
});
if (tranfserPageReqs && tranfserPageReqs.length > 0) {
goTo(OuterConfirmation, { reqs: tranfserPageReqs });
}
@ -311,58 +346,64 @@ function App() {
async function getSignRequests() {
const response = await fetchBackground({ method: "GET_SIGN_REQUESTS" });
const signRequests = response.data;
if (signRequests && signRequests.length > 0) {
goTo(MessageSignPage, { signRequests });
}
}
async function getAliasCreationRequests() {
const response = await fetchBackground({ method: "GET_ALIAS_CREATE_REQUESTS" });
console.log('alias creation requests', response);
const response = await fetchBackground({
method: "GET_ALIAS_CREATE_REQUESTS",
});
console.log("alias creation requests", response);
const createRequests = response.data;
if (createRequests && createRequests.length > 0) {
console.log('open alias create page');
if (createRequests && createRequests.length > 0) {
console.log("open alias create page");
goTo(AliasCreatePage, { createRequests });
}
}
async function getIonicSwapRequests() {
function getSwapAmountText(amount, asset) {
function getSwapAmountText(amount: number | Big, asset: { ticker: string }) {
const result = (
<>
<span className={swapModalStyles.swapAmount}>
{amount.toFixed()}
</span>
{" "}{asset.ticker}
</span>{" "}
{asset.ticker}
</>
);
return result;
}
const ionicSwapRes = await fetchBackground({ method: "GET_IONIC_SWAP_REQUESTS" });
const ionicSwapRes = await fetchBackground({
method: "GET_IONIC_SWAP_REQUESTS",
});
const swapRequests = ionicSwapRes.data;
const swapPageReqs = swapRequests.map(e => {
const {swap} = e;
const swapPageReqs = swapRequests.map((e: SwapRequest) => {
const { swap } = e;
const swapParams = {};
const swapParams: ({ address: string } | any) = {};
swapParams.address = swap.destinationAddress;
const receivingAsset = swap.destinationAsset;
const receivingAmount = new Big(swap.destinationAssetAmount);
swapParams.receiving = getSwapAmountText(receivingAmount, receivingAsset);
swapParams.receiving = getSwapAmountText(
receivingAmount,
receivingAsset as any
);
const sendingAsset = swap.currentAsset;
const sendingAmount = new Big(swap.currentAssetAmount);
swapParams.sending = getSwapAmountText(sendingAmount, sendingAsset);
swapParams.sending = getSwapAmountText(sendingAmount, sendingAsset as any);
return {
id: e.id,
method: "FINALIZE_IONIC_SWAP_REQUEST",
@ -370,78 +411,95 @@ function App() {
params: [
{
key: "Address",
value: swapParams.address ? Formatters.walletAddress(swapParams.address) : "???"
value: swapParams.address
? Formatters.walletAddress(swapParams.address)
: "???",
},
{
key: "Sending",
value: swapParams.sending || "???"
value: swapParams.sending || "???",
},
{
key: "Receiving",
value: swapParams.receiving || "???"
}
]
}
});
value: swapParams.receiving || "???",
},
],
};
});
const ionicSwapAcceptRes = await fetchBackground({ method: "GET_ACCEPT_IONIC_SWAP_REQUESTS" });
const ionicSwapAcceptRes = await fetchBackground({
method: "GET_ACCEPT_IONIC_SWAP_REQUESTS",
});
const acceptSwapReqs = ionicSwapAcceptRes.data;
console.log("ACCEPT SWAP", acceptSwapReqs);
const acceptPageReqs = await Promise.all(acceptSwapReqs.map(async e => {
const hex_raw_proposal = e?.hex_raw_proposal;
const acceptPageReqs = await Promise.all(
acceptSwapReqs.map(async (e: AcceptSwapReq) => {
const hex_raw_proposal = e?.hex_raw_proposal;
const swap = e?.swapProposal;
const swap = e?.swapProposal;
const swapParams = {};
const swapParams: ({ receiving: string } | any) = {};
function toBigWithDecimal(amount, decimalPoint) {
if (amount) {
return new Big(amount).div(new Big(10).pow(decimalPoint));
}
}
if (swap) {
const receivingAsset = e?.receivingAsset;
const receivingAmount = toBigWithDecimal(swap.to_finalizer[0]?.amount, receivingAsset.decimal_point);
if (receivingAmount !== undefined) {
swapParams.receiving = getSwapAmountText(receivingAmount, receivingAsset);
function toBigWithDecimal(amount: Big, decimalPoint: number) {
if (amount) {
return new Big(amount).div(new Big(10).pow(decimalPoint));
}
}
const sendingAsset = e?.sendingAsset;
const sendingAmount = toBigWithDecimal(swap.to_initiator[0]?.amount, sendingAsset.decimal_point);
if (swap) {
const receivingAsset = e?.receivingAsset;
const receivingAmount = toBigWithDecimal(
swap.to_finalizer[0]?.amount,
receivingAsset.decimal_point
);
if (sendingAmount !== undefined) {
swapParams.sending = getSwapAmountText(sendingAmount, sendingAsset);
if (receivingAmount !== undefined) {
swapParams.receiving = getSwapAmountText(
receivingAmount,
receivingAsset as any
);
}
const sendingAsset = e?.sendingAsset;
const sendingAmount = toBigWithDecimal(
swap.to_initiator[0]?.amount,
sendingAsset.decimal_point
);
if (sendingAmount !== undefined) {
swapParams.sending = getSwapAmountText(
sendingAmount,
sendingAsset as any
);
}
}
}
return {
id: e.id,
method: "FINALIZE_ACCEPT_IONIC_SWAP_REQUEST",
name: "Accept Ionic Swap",
params: [
{
key: "Hex Proposal",
value: Formatters.walletAddress(hex_raw_proposal)
},
{
key: "Sending",
value: swapParams.sending || "???"
},
{
key: "Receiving",
value: swapParams.receiving || "???"
},
]
}
}));
return {
id: e.id,
method: "FINALIZE_ACCEPT_IONIC_SWAP_REQUEST",
name: "Accept Ionic Swap",
params: [
{
key: "Hex Proposal",
value: Formatters.walletAddress(hex_raw_proposal),
},
{
key: "Sending",
value: swapParams.sending || "???",
},
{
key: "Receiving",
value: swapParams.receiving || "???",
},
],
};
})
);
const pageReqs = [...swapPageReqs, ...acceptPageReqs];
if (pageReqs && pageReqs.length > 0) {
goTo(OuterConfirmation, { reqs: pageReqs });
}
@ -456,7 +514,13 @@ function App() {
if (appConnected && !connectOpened && loggedIn && state.isConnected) {
modalLoad();
}
}, [appConnected, connectOpened, loggedIn, state.isConnected, state.wallet?.assets]);
}, [
appConnected,
connectOpened,
loggedIn,
state.isConnected,
state.wallet?.assets,
]);
useEffect(() => {
console.log("connectCredentials", state.connectCredentials);
@ -466,7 +530,7 @@ function App() {
credentials: {
token: state.connectCredentials.token,
port: state?.connectCredentials?.port || defaultPort,
},
} as any,
});
}
}, [state.connectCredentials]);
@ -479,10 +543,10 @@ function App() {
onConfirm={(password) => {
console.log(password, comparePasswords(password));
if (comparePasswords(password)) {
updateLoading(dispatch, true);
updateLoading(dispatch as dispatchType, true);
setTimeout(() => {
updateLoading(dispatch, false);
updateLoading(dispatch as dispatchType, false);
}, 2000);
setLoggedIn(true);
@ -490,7 +554,7 @@ function App() {
const connectData = ConnectKeyUtils.getConnectData(password);
console.log("connectData", connectData);
if (connectData?.token) {
setConnectData(dispatch, {
setConnectData(dispatch as dispatchType, {
token: connectData.token,
port: connectData.port,
});
@ -544,7 +608,7 @@ function App() {
if (connectKey)
ConnectKeyUtils.setConnectData(
connectKey,
walletPort,
String(walletPort),
password
);
setLoggedIn(true);
@ -557,4 +621,4 @@ function App() {
);
}
export default App;
export default App;

View file

@ -22,7 +22,7 @@ export default function AliasCreatePage() {
}, [createRequests]);
function nextRequest() {
if (reqIndex < createRequests.length - 1) {
if (reqIndex < createRequests.length - 1) {
setReqIndex(reqIndex + 1);
} else {
goBack();
@ -42,7 +42,7 @@ export default function AliasCreatePage() {
setDenying(false);
nextRequest();
}
return (
<div className={styles.signContainer}>
<h3 className={styles.title}>Create alias request</h3>

View file

@ -5,11 +5,17 @@ import questionIcon from "../../assets/svg/question.svg";
import { Store } from "../../store/store-reducer";
import s from "./AppPlug.module.scss";
const AppPlug = (props) => {
// Define the type for props
interface AppPlugProps {
setConnectOpened: (isOpened: boolean) => void;
}
const AppPlug: React.FC<AppPlugProps> = (props) => {
const { state } = useContext(Store);
const { setConnectOpened } = props;
// Type of btnClasses is inferred as string
const btnClasses = state.isLoading
? [s.plugButton, s.hidden].join(" ")
: s.plugButton;

View file

@ -1,7 +1,8 @@
import React from "react";
import Button from "../UI/Button/Button";
import s from "./ConnectPage.module.scss";
import logo from "../../assets/svg/logo.svg";
import { useContext, useEffect, useState } from "react";
import { Dispatch, SetStateAction, useContext, useEffect, useState } from "react";
import MyInput from "../UI/MyInput/MyInput";
import { fetchBackground, getSessionPassword } from "../../utils/utils";
import { setConnectData } from "../../store/actions";
@ -9,13 +10,22 @@ import { Store } from "../../store/store-reducer";
import ConnectKeyUtils from "../../utils/ConnectKeyUtils";
import { defaultPort } from "../../config/config";
interface ConnectPageProps {
incorrectPassword: boolean;
setIncorrectPassword: Dispatch<SetStateAction<boolean>>;
onConfirm?: (password?: string, keyValue?: string, walletPort?: string) => void;
passwordExists: boolean;
setConnectOpened: Dispatch<SetStateAction<boolean>>;
}
export default function ConnectPage({
incorrectPassword,
setIncorrectPassword,
onConfirm,
passwordExists,
setConnectOpened,
}) {
}: ConnectPageProps) {
const updateSettings = !!passwordExists;
const { dispatch } = useContext(Store);
@ -42,7 +52,7 @@ export default function ConnectPage({
getExistingPort();
}, [passwordExists]);
function onPasswordInput(event, repeat) {
function onPasswordInput(event: React.ChangeEvent<HTMLInputElement>, repeat: boolean) {
const { value } = event.currentTarget;
setIncorrectPassword(false);
setInvalidPassword(false);
@ -70,7 +80,7 @@ export default function ConnectPage({
credentials: { port: walletPort },
});
setConnectData(dispatch, {
setConnectData(dispatch as () => void, {
token: keyValue,
port: walletPort,
});
@ -89,7 +99,7 @@ export default function ConnectPage({
}
}
function onKeyInput(event) {
function onKeyInput(event: React.ChangeEvent<HTMLInputElement>) {
setKeyValue(event.currentTarget.value);
setKeyIncorrect(false);
}

View file

@ -1,3 +1,4 @@
import React from "react";
import { useContext, useState } from "react";
import arrowIcon from "../../assets/svg/arrow-shevron.svg";
import { useCensorDigits } from "../../hooks/useCensorDigits";
@ -22,11 +23,11 @@ const Header = () => {
}
};
const switchWallet = (id) => {
const switchWallet = (id: number | undefined) => {
// eslint-disable-next-line no-undef
chrome.storage.local.set({ key: id }, function () {
updateLoading(dispatch, true);
updateActiveWalletId(dispatch, id);
updateLoading(dispatch as () => void, true);
updateActiveWalletId(dispatch as () => void, String(id));
fetchBackground({
method: "SET_ACTIVE_WALLET",
@ -34,7 +35,7 @@ const Header = () => {
});
console.log("Active wallet set to", id);
setTimeout(() => updateLoading(dispatch, false), 1000);
setTimeout(() => updateLoading(dispatch as () => void, false), 1000);
});
toggleDropdown();

View file

@ -1,3 +1,4 @@
import React from "react";
import { getCurrent, goBack } from "react-chrome-extension-router";
import Button, { ButtonThemes } from "../UI/Button/Button";
import styles from "./MessageSignPage.module.scss";
@ -21,7 +22,7 @@ export default function MessageSignPage() {
}, [signRequests]);
function nextRequest() {
if (reqIndex < signRequests.length - 1) {
if (reqIndex < signRequests.length - 1) {
setReqIndex(reqIndex + 1);
} else {
goBack();
@ -41,7 +42,7 @@ export default function MessageSignPage() {
setDenying(false);
nextRequest();
}
return (
<div className={styles.signContainer}>
<h3 className={styles.title}>Sign Request</h3>

View file

@ -1,10 +1,16 @@
import React, { useCallback, useContext } from "react";
import cls from "../../components/ModalConfirmation/ModalConfirmation.module.scss";
import Modal from "../../components/UI/Modal/Modal";
import Modal from "../UI/Modal/Modal";
import Button, { ButtonThemes } from "../UI/Button/Button";
import { Store } from "../../store/store-reducer";
const ModalConfirmation = ({ isOpen, onClose, onConfirm }) => {
interface ModalConfirmationProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
}
const ModalConfirmation = ({ isOpen, onClose, onConfirm }: ModalConfirmationProps) => {
const { state } = useContext(Store);
const { method, params } = state.confirmationModal || {};
@ -30,7 +36,7 @@ const ModalConfirmation = ({ isOpen, onClose, onConfirm }) => {
<div className={cls.label}>params:</div>
<div className={cls.value}>
{params &&
params.map((param) => <span key={param}>{param}</span>)}
params.map((param: string) => <span key={param}>{param}</span>)}
</div>
</div>
</div>

View file

@ -1,6 +1,6 @@
import React, { useCallback, useContext } from "react";
import cls from "./ModalTransactionStatus.module.scss";
import Modal from "../../components/UI/Modal/Modal";
import Modal from "../UI/Modal/Modal";
import { Store } from "../../store/store-reducer";
import errorImage from "../../assets/svg/plus.svg";
import { updateTransactionStatus } from "../../store/actions";
@ -13,7 +13,7 @@ const ModalTransactionStatus = () => {
const { visible, type, code, message } = state?.transactionStatus;
const closeHandler = useCallback(() => {
updateTransactionStatus(dispatch, (prevState) => ({
updateTransactionStatus(dispatch as () => void, (prevState: object) => ({
...prevState,
isVisible: false,
}));
@ -34,7 +34,7 @@ const ModalTransactionStatus = () => {
if (type === "error") {
return (
<div className={cls.error}>
<div className={classNames(cls.icon, {}, [[cls.redColor]])}>
<div className={classNames(cls.icon, {}, [cls.redColor])}>
<img src={errorImage} alt="error" />
</div>
@ -69,7 +69,7 @@ const ModalTransactionStatus = () => {
<Modal
width={296}
isOpen={visible}
onClose={type !== "loading" && closeHandler}
onClose={type !== "loading" ? closeHandler : () => { }}
>
<div className={cls.ModalTransactionStatus}>
<Loading />

View file

@ -1,9 +1,15 @@
import React from "react";
import { getCurrent, goBack } from "react-chrome-extension-router";
import Button, { ButtonThemes } from "../UI/Button/Button";
import styles from "./OuterConfirmation.module.scss";
import { useState, useEffect } from "react";
import { fetchBackground } from "../../utils/utils";
interface ParamsType {
key: number;
value: string;
}
const OuterConfirmation = () => {
const { props } = getCurrent();
const { reqs } = props;
@ -27,7 +33,7 @@ const OuterConfirmation = () => {
}
}
async function acceptClick() {
setAccepting(true);
await fetchBackground({ method, id, success: true });
@ -50,7 +56,7 @@ const OuterConfirmation = () => {
<h5>{name}</h5>
<div className={styles.table}>
{
params.map((param) => {
params.map((param: ParamsType) => {
return (
<div className={styles.row} key={param.key}>

View file

@ -1,10 +1,17 @@
import React, { ChangeEvent } from "react";
import s from "./PasswordPage.module.scss";
import logo from "../../assets/svg/logo.svg";
import MyInput from "../UI/MyInput/MyInput";
import Button from "../UI/Button/Button";
import { useState } from "react";
function PasswordPage(props) {
interface PasswordPageProps {
onConfirm: (password: string) => void;
incorrectPassword: boolean;
setIncorrectPassword: (value: boolean) => void;
}
function PasswordPage(props: PasswordPageProps) {
const {
onConfirm,
incorrectPassword,
@ -13,7 +20,7 @@ function PasswordPage(props) {
const [password, setPassword] = useState("");
function onInputChange(event) {
function onInputChange(event: ChangeEvent<HTMLInputElement>) {
setIncorrectPassword(false);
setPassword(event.currentTarget.value);
}
@ -24,11 +31,11 @@ function PasswordPage(props) {
return (
<div className={s.passwordPage}>
<img
<img
className={s.logoImage}
src={logo}
alt="Zano"
src={logo}
alt="Zano"
/>
<p>Enter your password</p>
<div className={s.inputPanel}>

View file

@ -1,3 +1,4 @@
import React from "react";
import { useContext } from "react";
import crossIcon from "../../../assets/svg/cross.svg";
import bitcoinIcon from "../../../assets/tokens-svg/bitcoin.svg";
@ -9,7 +10,15 @@ import { Store } from "../../../store/store-reducer";
import s from "./Assets.module.scss";
import Decimal from "decimal.js";
const getIconImage = (asset) => {
interface Asset {
name: string;
ticker: string;
balance: number;
lockedBalance?: number;
value: number;
}
const getIconImage = (asset: Asset) => {
switch (asset.name) {
case "Zano":
return <img src={zanoIcon} alt="ZanoIcon" />;
@ -30,7 +39,7 @@ const Assets = () => {
return (
<div>
{state.wallet.assets.map((asset) => {
{(state.wallet.assets).map((asset) => {
const fiatBalance = (
Number(asset.balance) * state.priceData.price
).toFixed(2);

View file

@ -1,3 +1,4 @@
import React from "react";
import { useContext } from "react";
import Big from "big.js";
import LoadingIcon from "../../../assets/svg/loading.svg";
@ -9,9 +10,18 @@ import s from "./History.module.scss";
import NavLink from '../../UI/NavLink/NavLink';
import useGetAsset from "../../../hooks/useGetAsset";
interface HistoryItemProps {
transfer: {
assetId: string;
amount: string;
incoming: boolean;
};
fee: string;
isInitiator: boolean;
}
const HistoryItem = ({ transfer, fee, isInitiator }) => {
const {getAssetById} = useGetAsset();
const HistoryItem = ({ transfer, fee, isInitiator }: HistoryItemProps) => {
const { getAssetById } = useGetAsset();
if (transfer.amount === fee) return null;
const amount = new Big(transfer.amount);
@ -25,11 +35,11 @@ const HistoryItem = ({ transfer, fee, isInitiator }) => {
<p>
<span>
{transfer.assetId ===
"d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a"
? !isInitiator
? amount.toFixed()
: amount.minus(fixedFee).toFixed()
: amount.toFixed()
"d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a"
? !isInitiator
? amount.toFixed()
: amount.minus(fixedFee).toFixed()
: amount.toFixed()
}
</span>
{" "}
@ -61,8 +71,8 @@ const History = () => {
</div>
)}
{tx.transfers.map((transfer) => (
<HistoryItem transfer={transfer} fee={tx.fee} isInitiator={tx.isInitiator} />
{tx.transfers?.map((transfer) => (
<HistoryItem transfer={transfer as any} fee={String(tx.fee)} isInitiator={Boolean(tx.isInitiator)} />
))}
<span className={s.historyAddress}>{tx.txHash}</span>
</NavLink>
@ -72,4 +82,4 @@ const History = () => {
);
};
export default History;
export default History;

View file

@ -1,3 +1,4 @@
import React, { MouseEvent } from "react";
import { useState } from "react";
import Assets from "./Assets/Assets";
import History from "./History/History";
@ -11,9 +12,9 @@ const TokensTabs = () => {
{ label: "history", content: <History /> },
];
const toggleTabs = (e) => {
if (activeTab !== e.target.value) {
setActiveTab(Number(e.target.value));
const toggleTabs = (e: MouseEvent<HTMLButtonElement>) => {
if (activeTab !== Number(e.currentTarget.value)) {
setActiveTab(Number(e.currentTarget.value));
}
};

View file

@ -8,23 +8,54 @@ import RoutersNav from "../UI/RoutersNav/RoutersNav";
import { Store } from "../../store/store-reducer";
import styles from "./TransactionDetails.module.scss";
const TransactionDetails = (props) => {
type Transfer = {
amount: string;
assetId: string;
incoming: boolean;
};
type TransactionDetailsProps = {
transfers?: Transfer[];
fee: string;
addresses?: string[];
txHash: string;
blobSize: number;
timestamp: string;
height: number;
paymentId?: string | null;
comment: string;
isInitiator?: boolean;
};
type TableRowProps = {
label: string;
value?: string | number;
copyButton?: boolean;
children?: React.ReactNode;
};
type WhitelistedAssetType = {
asset_id: string;
ticker: string;
};
const TransactionDetails: React.FC<TransactionDetailsProps> = (props) => {
const { state } = useContext(Store);
const { copyToClipboard, SuccessCopyModal } = useCopy();
const { copyToClipboard } = useCopy(); // removed: SuccessCopyModal
useEffect(() => {
document.body.scrollTo(0, 0);
}, []);
const TableRow = ({ label, value, copyButton, children }) => {
const TableRow: React.FC<TableRowProps> = ({ label, value, copyButton, children }) => {
return (
<div className={`${copyButton && "table__row_button"} table__row`}>
<div className="table__label">
{label}:
{copyButton && (
{copyButton && value && (
<button
className="round-button"
onClick={() => copyToClipboard(value)}
onClick={() => copyToClipboard(value.toString())}
>
<img src={copyIcon} alt="copy icon" />
</button>
@ -38,30 +69,29 @@ const TransactionDetails = (props) => {
return (
<div>
{SuccessCopyModal}
{/* {SuccessCopyModal} */}
<RoutersNav title="Transaction details" />
<div className="table">
<TableRow label="Transfers">
<div className={styles.transaction__transfers}>
{props.transfers.map((transfer) => {
{props?.transfers?.map((transfer, index) => {
if (transfer.amount === props.fee) return null;
const amount = new Big(transfer.amount);
const fixedFee = new Big(props.fee);
return (
<div className={styles.transaction__transfer}>
<div key={index} className={styles.transaction__transfer}>
<p className="table__value">
{transfer.assetId ===
"d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a"
? !props.isInitiator
? amount.toFixed()
: amount.minus(fixedFee).toFixed()
: amount.toFixed()
}{" "}
: amount.toFixed()}{" "}
{
state.whitelistedAssets.find(
(asset) => asset.asset_id === transfer.assetId
(state.whitelistedAssets as any).find(
(asset: WhitelistedAssetType) => asset.asset_id === transfer.assetId
)?.ticker ?? "***"
}
</p>
@ -75,7 +105,6 @@ const TransactionDetails = (props) => {
);
})}
</div>
</TableRow>
<TableRow label="Fee" value={props.fee + " ZANO"} />
{props.addresses && (
@ -92,7 +121,7 @@ const TransactionDetails = (props) => {
{props.paymentId ? (
<TableRow label="Payment Id" value={props.paymentId} copyButton />
) : (
<TableRow label="Payment Id" value={props.paymentId} />
<TableRow label="Payment Id" value={props.paymentId ?? "N/A"} />
)}
<TableRow label="Comment" value={props.comment} />
</div>

View file

@ -4,8 +4,15 @@ import logo from "../../../assets/svg/logo.svg";
import { Store } from "../../../store/store-reducer";
import s from "./AppLoader.module.scss";
const AppLoader = ({ isSmall, firstWalletLoaded, loggedIn }) => {
interface AppLoaderProps {
isSmall?: boolean;
firstWalletLoaded: boolean;
loggedIn: boolean;
}
const AppLoader: React.FC<AppLoaderProps> = ({ isSmall, firstWalletLoaded, loggedIn }) => {
const { state } = useContext(Store);
const loaderClasses = isSmall ? [s.loader, s.small].join(" ") : s.loader;
return (
@ -26,4 +33,4 @@ const AppLoader = ({ isSmall, firstWalletLoaded, loggedIn }) => {
);
};
export default AppLoader;
export default AppLoader;

View file

@ -1,44 +0,0 @@
import React, { memo } from "react";
import cls from "./Button.module.scss";
import { classNames } from "../../../utils/classNames";
export const ButtonThemes = {
Primary: "primary",
Outline: "outline",
Clear: "clear",
};
export const Button = memo((props) => {
const {
className,
children,
theme = ButtonThemes.Primary,
href,
fullWidth,
...otherProps
} = props;
if (href) {
return (
<a
href={href}
className={classNames(cls.Button, {}, [className, cls[theme]])}
{...otherProps}
>
{children}
</a>
);
}
return (
<button
className={classNames(cls.Button, {}, [className, cls[theme]])}
type="button"
{...otherProps}
>
{children}
</button>
);
});
export default Button;

View file

@ -0,0 +1,63 @@
import React, { memo, ButtonHTMLAttributes, AnchorHTMLAttributes } from "react";
import cls from "./Button.module.scss";
import { classNames } from "../../../utils/classNames";
interface ButtonBaseProps {
className?: string;
children: React.ReactNode;
theme?: keyof ThemeProps | string;
fullWidth?: boolean;
}
interface ThemeProps {
Primary: "primary";
Outline: "outline";
Clear: "clear";
}
export const ButtonThemes: ThemeProps = {
Primary: "primary",
Outline: "outline",
Clear: "clear",
};
type ButtonProps = ButtonBaseProps &
(
| (ButtonHTMLAttributes<HTMLButtonElement> & { href?: never })
| (AnchorHTMLAttributes<HTMLAnchorElement> & { href: string })
);
export const Button = memo((props: ButtonProps) => {
const {
className,
children,
theme = ButtonThemes.Primary,
href,
fullWidth,
...otherProps
} = props;
if (href) {
return (
<a
href={href}
className={classNames(cls.Button, {}, [className, cls[theme]])}
{...(otherProps as AnchorHTMLAttributes<HTMLAnchorElement>)}
>
{children}
</a>
);
}
return (
<button
className={classNames(cls.Button, {}, [className, cls[theme]])}
type="button"
{...(otherProps as ButtonHTMLAttributes<HTMLButtonElement>)}
>
{children}
</button>
);
});
export default Button;

View file

@ -1,9 +1,17 @@
import React, { useCallback, useEffect, useState } from "react";
import React, { MouseEvent, ReactNode, useCallback, useEffect, useState } from "react";
import cls from "./Modal.module.scss";
import { createPortal } from "react-dom";
import { classNames } from '../../../utils/classNames';
const Modal = (props) => {
interface ModalProps {
className?: string;
children: ReactNode;
isOpen: boolean;
onClose: () => void;
width?: string | number;
}
const Modal = (props: ModalProps) => {
const { className, children, isOpen, onClose, width } = props;
const [isMounted, setIsMounted] = useState(false);
@ -21,7 +29,7 @@ const Modal = (props) => {
}, [onClose]);
const onKeyDown = useCallback(
(e) => {
(e: KeyboardEvent) => {
if (e.key === "Escape") {
closeHandler();
}
@ -39,7 +47,7 @@ const Modal = (props) => {
};
}, [isOpen, onKeyDown]);
const onContentClick = (e) => {
const onContentClick = (e: MouseEvent) => {
e.stopPropagation();
};
@ -54,7 +62,7 @@ const Modal = (props) => {
return createPortal(
<div className={classNames(cls.Modal, mods, [className])}>
<div onClick={closeHandler} className={cls.wrapper}>
<div onClick={onContentClick} style={{width}} className={cls.content}>
<div onClick={onContentClick} style={{ width }} className={cls.content}>
{children}
</div>
</div>

View file

@ -1,8 +1,12 @@
import React from "react";
import React, { InputHTMLAttributes } from "react";
import nextId from "react-id-generator";
import s from "./MyCheckbox.module.scss";
const MyCheckbox = ({ label, ...props }) => {
interface MyCheckboxProps extends InputHTMLAttributes<HTMLInputElement> {
label: string;
}
const MyCheckbox: React.FC<MyCheckboxProps> = ({ label, ...props }) => {
const id = nextId();
return (

View file

@ -1,9 +1,29 @@
import React, { memo } from "react";
import React, { memo, ChangeEvent, InputHTMLAttributes } from "react";
import nextId from "react-id-generator";
import cls from "./MyInput.module.scss";
import { classNames } from "../../../utils/classNames";
const MyInput = memo((props) => {
interface inputDataProps {
value?: string;
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
onInput?: (value: string) => void;
inputValid?: boolean;
onBlur?: () => void;
isDirty?: boolean;
isFilled?: boolean;
}
interface MyInputProps extends InputHTMLAttributes<HTMLInputElement> {
label?: string;
inputData?: inputDataProps;
isValid?: boolean;
noActiveBorder?: boolean;
isError?: boolean;
noValidation?: boolean;
type?: string;
}
const MyInput: React.FC<MyInputProps> = memo((props) => {
const id = nextId();
const {
label,
@ -15,18 +35,18 @@ const MyInput = memo((props) => {
noValidation,
...otherProps
} = props;
const { value, onChange, onInput, inputValid, onBlur, isDirty, isFilled } =
inputData;
const onInputHandler = (e) => {
const { value, onChange, onInput, inputValid, onBlur, isDirty, isFilled } = inputData || {};
const onInputHandler = (e: ChangeEvent<HTMLInputElement>) => {
if (type === "number" && !noValidation) {
const newValue = e.target.value
.replace(/[^0-9.]/g, "")
.replace(/(\..*?)\..*/g, "$1")
.replace(/^0[^.]/, "0");
onInput(newValue);
if (onInput) onInput(newValue);
} else {
onChange(e);
if (onChange) onChange(e);
}
};
@ -40,7 +60,7 @@ const MyInput = memo((props) => {
<div className={cls.myInput}>
<input
onBlur={onBlur}
onChange={(e) => onInputHandler(e)}
onChange={onInputHandler}
type={type}
id={id}
value={value}

View file

@ -2,7 +2,14 @@ import React from "react";
import { Link } from "react-chrome-extension-router";
import { classNames } from "../../../utils/classNames";
const NavLink = ({ component, children, className, ...props }) => {
interface NavLinkProps {
component: React.ComponentType<any>;
children: React.ReactNode;
className?: string;
props?: any;
}
const NavLink = ({ component, children, className, ...props }: NavLinkProps) => {
const scrollHandler = () => {
document.body.scrollTop = 0;
};

View file

@ -3,9 +3,14 @@ import { goBack } from "react-chrome-extension-router";
import backIcon from "../../../assets/svg/arrow-back.svg";
import s from "./RoutersNav.module.scss";
const RoutersNav = ({ title, onClick }) => {
interface RoutersNavProps {
title: string;
onClick?: "none" | (() => void | undefined);
}
const RoutersNav = ({ title, onClick }: RoutersNavProps) => {
const clickHandler = () => {
if (onClick) {
if (typeof onClick === "function") {
onClick();
} else {
goBack();

View file

@ -1,3 +1,4 @@
import React, { Dispatch, SetStateAction } from "react";
import { useContext, useRef, useState } from "react";
import copyIcon from "../../assets/svg/copy.svg";
import dotsIcon from "../../assets/svg/dots.svg";
@ -12,14 +13,14 @@ import { useCensorDigits } from "../../hooks/useCensorDigits";
import { useCopy } from "../../hooks/useCopy";
import { Store } from "../../store/store-reducer";
import { updateBalancesHidden, updateDisplay } from "../../store/actions";
import ModalTransactionStatus from "../../components/ModalTransactionStatus/ModalTransactionStatus";
import ModalTransactionStatus from "../ModalTransactionStatus/ModalTransactionStatus";
import WalletSend from "../WalletSend/WalletSend";
import WalletSettings from "../WalletSettings/WalletSettings";
import s from "./Wallet.module.scss";
import NavLink from "../UI/NavLink/NavLink";
import { classNames } from "../../utils/classNames";
const Wallet = ({ setConnectOpened }) => {
const Wallet = ({ setConnectOpened }: { setConnectOpened: Dispatch<SetStateAction<boolean>> }) => {
const { state, dispatch } = useContext(Store);
const { copied, copyToClipboard } = useCopy();
const { censorValue } = useCensorDigits();
@ -59,7 +60,7 @@ const Wallet = ({ setConnectOpened }) => {
)?.unlockedBalance;
const flipDisplay = () => {
updateDisplay(dispatch, !state.displayUsd);
updateDisplay(dispatch as any, !state.displayUsd as any);
};
const flipMenu = () => {
@ -121,7 +122,7 @@ const Wallet = ({ setConnectOpened }) => {
{getUnlockedBalance() !== state.wallet.balance && (
<span className={s.tooltipText}>
Locked balance:{" "}
{(Number(state.wallet.balance) - getUnlockedBalance()).toFixed(2)}{" "}
{(Number(state.wallet.balance) - Number(getUnlockedBalance())).toFixed(2)}{" "}
ZANO
</span>
)}

View file

@ -14,6 +14,31 @@ import { fetchBackground, validateTokensInput } from "../../utils/utils";
import AssetsSelect from "./ui/AssetsSelect/AssetsSelect";
import AdditionalDetails from "./ui/AdditionalDetails/AdditionalDetails";
interface ResponseData {
data?: any;
error?: string;
}
interface FetchBackgroundParams {
method: string;
assetId: string;
destination: string;
amount: string | number;
comment: string;
decimalPoint: number;
password?: string;
id?: number;
success?: boolean;
credentials?: {
port: string;
};
}
interface AssetProps {
unlockedBalance: number;
balance: number;
}
const WalletSend = () => {
const { state } = useContext(Store);
const [activeStep, setActiveStep] = useState(0);
@ -36,19 +61,25 @@ const WalletSend = () => {
const isSenderInfo = useCheckbox(false);
const isReceiverInfo = useCheckbox(false);
const sendTransfer = (destination, amount, comment, assetId, decimalPoint) => {
const sendTransfer = (
destination: string,
amount: string | number,
comment: string,
assetId: string,
decimalPoint: number
): Promise<any> => {
return new Promise(async (resolve, reject) => {
// eslint-disable-next-line no-undef
if (chrome.runtime.sendMessage) {
if (chrome.runtime.sendMessage as any) {
// eslint-disable-next-line no-undef
const response = await fetchBackground({
const response: ResponseData = await fetchBackground({
method: "SEND_TRANSFER",
assetId,
destination,
amount,
comment,
decimalPoint,
});
} as FetchBackgroundParams);
if (response.data) {
resolve(response.data);
@ -63,7 +94,7 @@ const WalletSend = () => {
});
};
const openExplorer = (txId) => {
const openExplorer = (txId: string) => {
// eslint-disable-next-line no-undef
chrome.tabs.create({
url: `https://testnet-explorer.zano.org/block/${txId}`,
@ -72,8 +103,8 @@ const WalletSend = () => {
useEffect(() => {
(async () => {
if (address.value.startsWith("@")) {
const alias = address.value.slice(1);
if (String(address.value).startsWith("@")) {
const alias = String(address.value).slice(1);
const resolvedAddress = await fetchAddress(alias);
if (resolvedAddress) {
setSubmitAddress(resolvedAddress);
@ -81,8 +112,8 @@ const WalletSend = () => {
setSubmitAddress("");
}
} else {
if (address.value.length === 97) {
setSubmitAddress(address.value);
if (String(address.value).length === 97) {
setSubmitAddress(String(address.value));
} else {
setSubmitAddress("");
}
@ -91,20 +122,20 @@ const WalletSend = () => {
}, [address.value]);
useEffect(() => {
const isValid = validateTokensInput(amount.value, asset.decimalPoint);
const isValid = !!validateTokensInput(amount.value, Number(asset.decimalPoint));
setAmountValid(isValid);
}, [amount.value, asset]);
const fetchAddress = async (alias) => await fetchBackground({ method: "GET_ALIAS_DETAILS", alias });
const fetchAddress = async (alias: string) => await fetchBackground({ method: "GET_ALIAS_DETAILS", alias });
const checkAvailableBalance = (amount, asset) =>
const checkAvailableBalance = (amount: string | number, asset: AssetProps) =>
asset.unlockedBalance !== asset.balance
? +amount <= asset.unlockedBalance - fee.value
? +amount <= asset.unlockedBalance - Number(fee.value)
: true;
//-------------------------------------------------------------------------------------------------------------------
// Subcomponents
const TableRow = ({ label, value }) => {
const TableRow = ({ label, value }: { label: string; value: string }) => {
return (
<div className="table__row">
<div className="table__label">{label}:</div>
@ -113,7 +144,7 @@ const WalletSend = () => {
);
};
return (
<>
{(() => {
@ -131,21 +162,21 @@ const WalletSend = () => {
<MyInput
placeholder="Address or alias"
label="Address"
inputData={address}
inputData={address as any}
isValid={!!submitAddress}
/>
<AssetsSelect value={asset} setValue={setAsset} />
<AssetsSelect value={asset} setValue={setAsset as React.Dispatch<React.SetStateAction<any>>} />
<MyInput
type="number"
placeholder="Amount to transfer"
label="Amount:"
inputData={amount}
isError={amount.value && !amountValid}
inputData={amount as any}
isError={amount.value ? !amountValid : false}
/>
<MyInput
placeholder="Enter the comment"
label="Comment:"
inputData={comment}
inputData={comment as any}
/>
<AdditionalDetails
mixin={mixin}
@ -159,7 +190,7 @@ const WalletSend = () => {
!submitAddress ||
!amount.value ||
!amountValid ||
!checkAvailableBalance(amount.value, asset)
!checkAvailableBalance(amount.value, asset as any)
}
>
Send
@ -179,9 +210,9 @@ const WalletSend = () => {
value={amount?.value + " " + asset?.ticker}
/>
<TableRow label="From" value={state?.wallet?.address} />
<TableRow label="To" value={address.value} />
<TableRow label="Comment" value={comment?.value} />
<TableRow label="Fee" value={fee?.value} />
<TableRow label="To" value={String(address.value)} />
<TableRow label="Comment" value={String(comment?.value)} />
<TableRow label="Fee" value={String(fee?.value)} />
</div>
<Button
@ -189,9 +220,9 @@ const WalletSend = () => {
const transferStatus = await sendTransfer(
submitAddress,
amount.value,
comment.value,
asset.assetId,
asset.decimalPoint
String(comment.value),
String(asset.assetId),
Number(asset.decimalPoint)
);
console.log("transfer status", transferStatus);
if (transferStatus.result) {

View file

@ -5,7 +5,39 @@ import s from "./AdditionalDetails.module.scss";
import { classNames } from "../../../../utils/classNames";
import arrowIcon from "../../../../assets/svg/arrow-select.svg";
const AdditionalDetails = ({ fee, mixin, isSenderInfo, isReceiverInfo }) => {
interface mixinType {
isEmpty: boolean;
minLengthError: boolean;
amountCorrectError: boolean;
inputValid: boolean;
value: string | number;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
onInput: (newValue: string | number) => void;
onBlur: () => void;
isFilled: boolean;
isDirty: boolean;
}
interface feeType {
isEmpty: boolean;
minLengthError: boolean;
amountCorrectError: boolean;
inputValid: boolean;
value: string | number;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
onInput: (newValue: string | number) => void;
onBlur: () => void;
isFilled: boolean;
isDirty: boolean;
}
interface AdditionalDetailsProps {
fee: string | number | feeType;
mixin: string | number | mixinType;
isSenderInfo: { isChecked: boolean; onChange: () => void };
isReceiverInfo: { isChecked: boolean; onChange: () => void };
}
const AdditionalDetails = ({ fee, mixin, isSenderInfo, isReceiverInfo }: AdditionalDetailsProps) => {
const [detailsVisible, setDetailsVisible] = useState(false);
const toggleDetails = () => {
@ -27,8 +59,8 @@ const AdditionalDetails = ({ fee, mixin, isSenderInfo, isReceiverInfo }) => {
{detailsVisible && (
<div className={s.detailsSelectContent}>
<div className={s.detailsSelectInputs}>
<MyInput label="Mixin:" inputData={mixin} disabled />
<MyInput label="Fee:" inputData={fee} disabled />
<MyInput label="Mixin:" inputData={mixin as any} disabled />
<MyInput label="Fee:" inputData={fee as any} disabled />
</div>
<div className={s.detailsSelectCheckboxes}>
<MyCheckbox

View file

@ -9,13 +9,22 @@ import mainStyles from "../../WalletSend.module.scss";
import s from "./AssetsSelect.module.scss";
import { classNames } from "../../../../utils/classNames";
const AssetsSelect = ({ value, setValue }) => {
interface Asset {
name: string;
}
interface AssetsSelectProps {
value: Asset;
setValue: (asset: Asset) => void;
}
const AssetsSelect = ({ value, setValue }: AssetsSelectProps) => {
const { state } = useContext(Store);
const [isOpen, setIsOpen] = useState(false);
const [focusedIndex, setFocusedIndex] = React.useState(null);
const selectRef = useRef(null);
const [focusedIndex, setFocusedIndex] = React.useState<number | null>(null);
const selectRef = useRef<HTMLDivElement>(null);
const handleKeyDown = (e) => {
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "ArrowDown") {
e.preventDefault();
if (
@ -24,21 +33,24 @@ const AssetsSelect = ({ value, setValue }) => {
) {
setFocusedIndex(0);
} else {
setFocusedIndex((prevIndex) => prevIndex + 1);
setFocusedIndex((prevIndex) => Number(prevIndex) + 1);
}
} else if (e.key === "ArrowUp") {
e.preventDefault();
if (focusedIndex === null || focusedIndex === 0) {
setFocusedIndex(state.wallet.assets.length - 1);
} else {
setFocusedIndex((prevIndex) => prevIndex - 1);
setFocusedIndex((prevIndex) => Number(prevIndex) - 1);
}
}
};
useEffect(() => {
if (focusedIndex !== null && selectRef.current) {
selectRef.current.childNodes[focusedIndex].focus();
const childNodes = selectRef.current.childNodes;
if (childNodes && childNodes[focusedIndex as number]) {
(childNodes[focusedIndex as number] as HTMLElement).focus();
}
}
}, [focusedIndex]);
@ -46,12 +58,12 @@ const AssetsSelect = ({ value, setValue }) => {
setIsOpen(!isOpen);
}
function setValueHandler(asset) {
function setValueHandler(asset: Asset) {
setValue(asset);
setIsOpen(false);
}
const getAssetImage = (name) => {
const getAssetImage = (name: string) => {
switch (name) {
case "Zano":
return zanoIcon;

View file

@ -7,7 +7,7 @@ import s from "./WalletSettings.module.scss";
const WalletSettings = () => {
const [isBtnDisabled, setIsBtnDisabled] = useState(true);
const localNodePort = useInput("11112");
const localNodePort = useInput("11112", {});
return (
<div>

View file

@ -1,16 +1,18 @@
import { useEffect, useRef } from "react";
const useAwayClick = (ref, callback) => {
const savedCallback = useRef();
type Callback = () => void;
const useAwayClick = (ref: React.RefObject<HTMLElement>, callback: Callback) => {
const savedCallback = useRef<Callback | undefined>();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
const handleClick = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
savedCallback.current();
const handleClick = (event: MouseEvent) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
savedCallback.current?.();
}
};

View file

@ -6,10 +6,10 @@ export function useCensorDigits() {
const { state, dispatch } = useContext(Store);
const changeCensor = () => {
updateBalancesHidden(dispatch, (prevState) => !prevState);
updateBalancesHidden(dispatch, (prevState: boolean) => !prevState);
};
const censorValue = (number) => {
const censorValue = (number: number | string): string | number => {
if (state.isBalancesHidden) {
return number.toString().replace(/\d/g, "*");
} else {
@ -19,3 +19,4 @@ export function useCensorDigits() {
return { changeCensor, censorValue };
}

View file

@ -1,6 +1,6 @@
import { useState } from "react";
export const useCheckbox = (initialState) => {
export const useCheckbox = (initialState: boolean) => {
const [isChecked, setIsChecked] = useState(initialState);
const onChange = () => {

View file

@ -4,7 +4,7 @@ import { useState } from "react";
export const useCopy = () => {
const [copied, setCopied] = useState(false);
const copyToClipboard = (text) => {
const copyToClipboard = (text: string) => {
copy(text);
if (!copied) {
setCopied(true);

View file

@ -1,13 +0,0 @@
import { useContext } from "react";
import { Store } from "../store/store-reducer";
export default function useGetAsset() {
const {state} = useContext(Store);
function getAssetById(id) {
return state.wallet.assets.find(asset => asset.assetId === id);
}
return { getAssetById };
}

View file

@ -0,0 +1,16 @@
import { useContext } from "react";
import { Store } from "../store/store-reducer";
interface Asset {
assetId: string;
}
export default function useGetAsset() {
const { state } = useContext(Store);
function getAssetById(id: string) {
return state.wallet.assets.find((asset: Asset | any) => asset.assetId === id);
}
return { getAssetById };
}

View file

@ -1,24 +0,0 @@
import { useState } from "react";
import { useValidation } from "./useValidation";
export const useInput = (initialState, validations) => {
const [value, setValue] = useState(initialState);
const [isDirty, setIsDirty] = useState(false);
const valid = useValidation(value, validations);
const onChange = (e) => {
setValue(e.target.value);
};
const onInput = (value) => {
setValue(value);
};
const onBlur = () => {
setIsDirty(true);
};
const isFilled = value.length > 0;
return { value, onChange, onInput, onBlur, isFilled, isDirty, ...valid };
};

34
src/app/hooks/useInput.ts Normal file
View file

@ -0,0 +1,34 @@
import { useState } from "react";
import { useValidation } from "./useValidation";
type Validations = {
minLength?: number;
isEmpty?: boolean;
isAmountCorrect?: boolean;
customValidation?: boolean;
};
export const useInput = (
initialState: string | number,
validations: Validations
) => {
const [value, setValue] = useState<string | number>(initialState);
const [isDirty, setIsDirty] = useState<boolean>(false);
const valid = useValidation(value, validations);
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};
const onInput = (newValue: string | number) => {
setValue(newValue);
};
const onBlur = () => {
setIsDirty(true);
};
const isFilled = typeof value === "string" && value.length > 0;
return { value, onChange, onInput, onBlur, isFilled, isDirty, ...valid };
};

View file

@ -1,48 +0,0 @@
import { useEffect, useState } from "react";
export const useValidation = (value, validations) => {
const [isEmpty, setIsEmpty] = useState(true);
const [minLengthError, setMinLengthError] = useState(false);
const [amountCorrectError, setAmountCorrectError] = useState(false);
const [inputValid, setInputValid] = useState(false);
useEffect(() => {
for (const validation in validations) {
switch (validation) {
case "minLength":
value.length < validations[validation]
? setMinLengthError(true)
: setMinLengthError(false);
break;
case "isEmpty":
value ? setIsEmpty(false) : setIsEmpty(true);
break;
case "isAmountCorrect":
const amountCheckResult =
!isNaN(value) && value >= 0.000000000001 && value <= 1000000000;
setAmountCorrectError(!amountCheckResult);
break;
case "customValidation":
setInputValid(true);
break;
default:
break;
}
}
}, [validations, value]);
useEffect(() => {
if (isEmpty || minLengthError || amountCorrectError) {
setInputValid(false);
} else {
setInputValid(true);
}
}, [isEmpty, minLengthError, amountCorrectError]);
return {
isEmpty,
minLengthError,
amountCorrectError,
inputValid,
};
};

View file

@ -0,0 +1,60 @@
import { useEffect, useState } from "react";
type Validations = {
minLength?: number;
isEmpty?: boolean;
isAmountCorrect?: boolean;
customValidation?: boolean;
};
export const useValidation = (value: string | number, validations: Validations) => {
const [isEmpty, setIsEmpty] = useState<boolean>(true);
const [minLengthError, setMinLengthError] = useState<boolean>(false);
const [amountCorrectError, setAmountCorrectError] = useState<boolean>(false);
const [inputValid, setInputValid] = useState<boolean>(false);
useEffect(() => {
for (const validation in validations) {
switch (validation) {
case "minLength":
if (typeof value === "string" && value.length < validations[validation]!) {
setMinLengthError(true);
} else {
setMinLengthError(false);
}
break;
case "isEmpty":
setIsEmpty(!value);
break;
case "isAmountCorrect":
const amountCheckResult =
typeof value === "number" &&
!isNaN(value) &&
value >= 0.000000000001 &&
value <= 1000000000;
setAmountCorrectError(!amountCheckResult);
break;
case "customValidation":
setInputValid(true);
break;
default:
break;
}
}
}, [validations, value]);
useEffect(() => {
if (isEmpty || minLengthError || amountCorrectError) {
setInputValid(false);
} else {
setInputValid(true);
}
}, [isEmpty, minLengthError, amountCorrectError]);
return {
isEmpty,
minLengthError,
amountCorrectError,
inputValid,
};
};

View file

@ -1,83 +0,0 @@
export const updateWalletsList = (dispatch, state) => {
return dispatch({
type: "WALLETS_LIST_UPDATED",
payload: state,
});
};
export const updateWalletData = (dispatch, state) => {
return dispatch({
type: "WALLET_DATA_UPDATED",
payload: state,
});
};
export const updateWalletConnected = (dispatch, state) => {
return dispatch({
type: "WALLET_CONNECTED_UPDATED",
payload: state,
});
};
export const updateActiveWalletId = (dispatch, state) => {
return dispatch({
type: "ACTIVE_WALLET_ID_UPDATED",
payload: state,
});
};
export const updatePriceData = (dispatch, state) => {
return dispatch({
type: "PRICE_DATA_UPDATED",
payload: state,
});
};
export const updateDisplay = (dispatch, state) => {
return dispatch({
type: "DISPLAY_CURRENCY_UPDATED",
payload: state,
});
};
export const updateLoading = (dispatch, state) => {
return dispatch({
type: "LOADING_UPDATED",
payload: state,
});
};
export const updateBalancesHidden = (dispatch, state) => {
return dispatch({
type: "BALANCES_HIDDEN_UPDATED",
payload: state,
});
};
export const updateConfirmationModal = (dispatch, state) => {
return dispatch({
type: "CONFIRMATION_MODAL_UPDATED",
payload: state,
});
};
export const updateTransactionStatus = (dispatch, state) => {
return dispatch({
type: "TRANSACTION_STATUS_UPDATED",
payload: state,
});
};
export const setConnectData = (dispatch, state) => {
return dispatch({
type: "SET_CONNECT_DATA",
payload: state
});
};
export const setWhiteList = (dispatch, state) => {
return dispatch({
type: "SET_WHITE_LIST",
payload: state
});
}

107
src/app/store/actions.ts Normal file
View file

@ -0,0 +1,107 @@
// Define types for the structure of your state
interface WalletState {
wallets: string[];
walletData: object;
isConnected: boolean;
activeWalletId: string | number;
priceData: object;
displayCurrency: string;
isLoading: boolean;
balancesHidden: boolean;
confirmationModalOpen: boolean | null | {method: string; params: string[]};
transactionStatus: string;
connectData: object;
whiteList: string[];
}
type DispatchFunction = (action: { type: string; payload: WalletState[keyof WalletState] }) => void;
export const updateWalletsList = (dispatch: DispatchFunction, state: WalletState['wallets']): void => {
return dispatch({
type: "WALLETS_LIST_UPDATED",
payload: state,
});
};
export const updateWalletData = (dispatch: DispatchFunction, state: WalletState['walletData']): void => {
return dispatch({
type: "WALLET_DATA_UPDATED",
payload: state,
});
};
export const updateWalletConnected = (dispatch: DispatchFunction, state: WalletState['isConnected']): void => {
return dispatch({
type: "WALLET_CONNECTED_UPDATED",
payload: state,
});
};
export const updateActiveWalletId = (dispatch: DispatchFunction, state: WalletState['activeWalletId']): void => {
return dispatch({
type: "ACTIVE_WALLET_ID_UPDATED",
payload: state,
});
};
export const updatePriceData = (dispatch: DispatchFunction, state: WalletState['priceData']): void => {
return dispatch({
type: "PRICE_DATA_UPDATED",
payload: state,
});
};
export const updateDisplay = (dispatch: DispatchFunction, state: WalletState['displayCurrency']): void => {
return dispatch({
type: "DISPLAY_CURRENCY_UPDATED",
payload: state,
});
};
export const updateLoading = (dispatch: DispatchFunction, state: WalletState['isLoading']): void => {
return dispatch({
type: "LOADING_UPDATED",
payload: state,
});
};
export const updateBalancesHidden = (dispatch: any, state: any): void => {
return dispatch({
type: "BALANCES_HIDDEN_UPDATED",
payload: state,
});
};
export const updateConfirmationModal = (dispatch: DispatchFunction, state: WalletState['confirmationModalOpen']): void => {
return dispatch({
type: "CONFIRMATION_MODAL_UPDATED",
payload: state,
});
};
export const updateTransactionStatus = (dispatch: DispatchFunction, state: WalletState['transactionStatus'] | any): void => {
return dispatch({
type: "TRANSACTION_STATUS_UPDATED",
payload: state,
});
};
interface ConnectCredentials {
token: string;
port: string;
}
export const setConnectData = (dispatch: DispatchFunction, state: ConnectCredentials): void => {
return dispatch({
type: "SET_CONNECT_DATA",
payload: state
});
};
export const setWhiteList = (dispatch: DispatchFunction, state: WalletState['whiteList']): void => {
return dispatch({
type: "SET_WHITE_LIST",
payload: state
});
};

View file

@ -1,132 +0,0 @@
import { createContext, useReducer } from "react";
const initialState = {
walletsList: [
{
address:
"ZxDTZ8LJ88ZK6Ja1P9iqDNgCiBM6FhiBKdDoTAoEp9nY9q8d846iePAGYGjNvrU9uFHDXD3by5CooSBrsXBDfE9M11WBwAxQ9",
alias: "ravaga",
balance: 1337,
},
{
address:
"ZxDCEeVaHsYXEJotN8Q5y4PW7Y4inrNaibqpmU7P9KGCZ76LBPYkn9Gf8BzCSLSJfpVDJ7GzBPApGEK4BVbogZwN2opPAQDfU",
alias: "test",
balance: 27,
},
],
activeWalletId: 0,
wallet: {
address:
"ZxDTZ8LJ88ZK6Ja1P9iqDNgCiBM6FhiBKdDoTAoEp9nY9q8d846iePAGYGjNvrU9uFHDXD3by5CooSBrsXBDfE9M11WBwAxQ9",
alias: "ravaga",
balance: 1337,
lockedBalance: 0,
assets: [
{
name: "Zano",
ticker: "ZANO",
balance: 1337,
lockedBalance: 0,
value: 1000,
},
{
name: "Wrapped Bitcoin",
ticker: "WBTC",
balance: 0.212,
value: 4096.96,
},
{
name: "Wrapped Ethereum",
ticker: "WETH",
balance: 2.1,
value: 3020.12,
},
{
name: "Confidential Token",
ticker: "CT",
balance: 15.52,
value: 672.84,
},
],
transactions: [
{
isConfirmed: true,
incoming: true,
amount: 100,
ticker: "ZANO",
address:
"ZxDTZ8LJ88ZK6Ja1P9iqDNgCiBM6FhiBKdDoTAoEp9nY9q8d846iePAGYGjNvrU9uFHDXD3by5CooSBrsXBDfE9M11WBwAxQ9",
},
{
isConfirmed: false,
incoming: false,
value: 17,
ticker: "ZANO",
address:
"ZxDTZ8LJ88ZK6Ja1P9iqDNgCiBM6FhiBKdDoTAoEp9nY9q8d846iePAGYGjNvrU9uFHDXD3by5CooSBrsXBDfE9M11WBwAxQ9",
},
],
},
displayUsd: false,
isLoading: true,
isConnected: undefined,
isBalancesHidden: false,
priceData: { price: 1, change: -4.6 },
confirmationModal: null,
transactionStatus: {
visible: false,
type: "",
code: 0,
message: "",
},
connectCredentials: {
token: null,
port: null,
},
whitelistedAssets: [],
};
const reducer = (state, action) => {
switch (action.type) {
case "WALLET_ADDRESS_UPDATED":
return { ...state, walletAddress: action.payload };
case "WALLET_BALANCE_UPDATED":
return { ...state, walletBalance: action.payload };
case "WALLET_CONNECTED_UPDATED":
return { ...state, isConnected: action.payload };
case "WALLETS_LIST_UPDATED":
return { ...state, walletsList: action.payload };
case "ACTIVE_WALLET_ID_UPDATED":
return { ...state, activeWalletId: action.payload };
case "WALLET_DATA_UPDATED":
return { ...state, wallet: action.payload };
case "PRICE_DATA_UPDATED":
return { ...state, priceData: action.payload };
case "DISPLAY_CURRENCY_UPDATED":
return { ...state, displayUsd: action.payload };
case "LOADING_UPDATED":
return { ...state, isLoading: action.payload };
case "BALANCES_HIDDEN_UPDATED":
return { ...state, isBalancesHidden: action.payload };
case "CONFIRMATION_MODAL_UPDATED":
return { ...state, confirmationModal: action.payload };
case "TRANSACTION_STATUS_UPDATED":
return { ...state, transactionStatus: action.payload };
case "SET_CONNECT_DATA":
return { ...state, connectCredentials: action.payload }
case "SET_WHITE_LIST":
return { ...state, whitelistedAssets: action.payload }
default:
return state;
}
};
export const Store = createContext(initialState);
export const StoreProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<Store.Provider value={{ state, dispatch }}>{children}</Store.Provider>
);
};

View file

@ -0,0 +1,253 @@
import React, { createContext, useReducer, ReactNode, useContext } from "react";
// Define the types for the state
interface Asset {
name: string;
ticker: string;
balance: number;
lockedBalance?: number;
unlockedBalance?: number;
value: number;
decimalPoint?: number;
assetId?: string;
}
interface Transfer {
assetId?: string;
amount?: number;
incoming?: boolean;
}
interface Transaction {
txHash?: string;
isConfirmed: boolean;
incoming: boolean;
amount?: number;
value?: number;
ticker: string;
address: string;
transfers?: Transfer[];
isInitiator?: boolean;
fee?: number | string;
}
interface Wallet {
address: string;
alias: string;
balance: number;
lockedBalance?: number;
assets: Asset[];
transactions: Transaction[];
}
interface TransactionStatus {
visible: boolean;
type: string;
code: number;
message: string;
}
interface ConnectCredentials {
token: string | null;
port: string | null;
}
interface PriceData {
price: number;
change: number;
}
interface State {
walletsList: { address: string; alias: string; balance: number, wallet_id?: number; }[];
activeWalletId: number;
wallet: Wallet;
displayUsd: boolean;
isLoading: boolean;
isConnected: boolean | undefined;
isBalancesHidden: boolean;
priceData: PriceData;
confirmationModal: string | null | any;
transactionStatus: TransactionStatus;
connectCredentials: ConnectCredentials;
whitelistedAssets: string[];
walletAddress?: string;
walletBalance?: number;
}
// Initial state
const initialState: State = {
walletsList: [
{
address:
"ZxDTZ8LJ88ZK6Ja1P9iqDNgCiBM6FhiBKdDoTAoEp9nY9q8d846iePAGYGjNvrU9uFHDXD3by5CooSBrsXBDfE9M11WBwAxQ9",
alias: "ravaga",
balance: 1337,
},
{
address:
"ZxDCEeVaHsYXEJotN8Q5y4PW7Y4inrNaibqpmU7P9KGCZ76LBPYkn9Gf8BzCSLSJfpVDJ7GzBPApGEK4BVbogZwN2opPAQDfU",
alias: "test",
balance: 27,
},
],
activeWalletId: 0,
wallet: {
address:
"ZxDTZ8LJ88ZK6Ja1P9iqDNgCiBM6FhiBKdDoTAoEp9nY9q8d846iePAGYGjNvrU9uFHDXD3by5CooSBrsXBDfE9M11WBwAxQ9",
alias: "ravaga",
balance: 1337,
lockedBalance: 0,
assets: [
{
name: "Zano",
ticker: "ZANO",
balance: 1337,
lockedBalance: 0,
value: 1000,
},
{
name: "Wrapped Bitcoin",
ticker: "WBTC",
balance: 0.212,
value: 4096.96,
},
{
name: "Wrapped Ethereum",
ticker: "WETH",
balance: 2.1,
value: 3020.12,
},
{
name: "Confidential Token",
ticker: "CT",
balance: 15.52,
value: 672.84,
},
],
transactions: [
{
isConfirmed: true,
incoming: true,
amount: 100,
ticker: "ZANO",
address:
"ZxDTZ8LJ88ZK6Ja1P9iqDNgCiBM6FhiBKdDoTAoEp9nY9q8d846iePAGYGjNvrU9uFHDXD3by5CooSBrsXBDfE9M11WBwAxQ9",
},
{
isConfirmed: false,
incoming: false,
value: 17,
ticker: "ZANO",
address:
"ZxDTZ8LJ88ZK6Ja1P9iqDNgCiBM6FhiBKdDoTAoEp9nY9q8d846iePAGYGjNvrU9uFHDXD3by5CooSBrsXBDfE9M11WBwAxQ9",
},
],
},
displayUsd: false,
isLoading: true,
isConnected: undefined,
isBalancesHidden: false,
priceData: { price: 1, change: -4.6 },
confirmationModal: null,
transactionStatus: {
visible: false,
type: "",
code: 0,
message: "",
},
connectCredentials: {
token: null,
port: null,
},
whitelistedAssets: [],
};
type Action =
| { type: "WALLET_ADDRESS_UPDATED"; payload: string }
| { type: "WALLET_BALANCE_UPDATED"; payload: number }
| { type: "WALLET_CONNECTED_UPDATED"; payload: boolean | undefined }
| { type: "WALLETS_LIST_UPDATED"; payload: { address: string; alias: string; balance: number }[] }
| { type: "ACTIVE_WALLET_ID_UPDATED"; payload: number }
| { type: "WALLET_DATA_UPDATED"; payload: Wallet }
| { type: "PRICE_DATA_UPDATED"; payload: PriceData }
| { type: "DISPLAY_CURRENCY_UPDATED"; payload: boolean }
| { type: "LOADING_UPDATED"; payload: boolean }
| { type: "BALANCES_HIDDEN_UPDATED"; payload: boolean }
| { type: "CONFIRMATION_MODAL_UPDATED"; payload: string | null }
| { type: "TRANSACTION_STATUS_UPDATED"; payload: TransactionStatus }
| { type: "SET_CONNECT_DATA"; payload: ConnectCredentials }
| { type: "SET_WHITE_LIST"; payload: string[] }
| { type: "SET_BALANCES_HIDDEN"; payload: boolean };
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "WALLET_ADDRESS_UPDATED":
return { ...state, walletAddress: action.payload };
case "WALLET_BALANCE_UPDATED":
return { ...state, walletBalance: action.payload };
case "WALLET_CONNECTED_UPDATED":
return { ...state, isConnected: action.payload };
case "WALLETS_LIST_UPDATED":
return { ...state, walletsList: action.payload };
case "ACTIVE_WALLET_ID_UPDATED":
return { ...state, activeWalletId: action.payload };
case "WALLET_DATA_UPDATED":
return { ...state, wallet: action.payload };
case "PRICE_DATA_UPDATED":
return { ...state, priceData: action.payload };
case "DISPLAY_CURRENCY_UPDATED":
return { ...state, displayUsd: action.payload };
case "LOADING_UPDATED":
return { ...state, isLoading: action.payload };
case "BALANCES_HIDDEN_UPDATED":
return { ...state, isBalancesHidden: action.payload };
case "CONFIRMATION_MODAL_UPDATED":
return { ...state, confirmationModal: action.payload };
case "TRANSACTION_STATUS_UPDATED":
return { ...state, transactionStatus: action.payload };
case "SET_CONNECT_DATA":
return { ...state, connectCredentials: action.payload };
case "SET_WHITE_LIST":
return { ...state, whitelistedAssets: action.payload };
default:
return state;
}
};
export const Store = createContext<{ state: State; dispatch: React.Dispatch<Action> }>({
state: initialState,
dispatch: () => { },
});
interface StoreProviderProps {
children: ReactNode;
}
export const StoreProvider = ({ children }: StoreProviderProps) => {
const [state, dispatch] = useReducer(reducer, initialState);
return <Store.Provider value={{ state, dispatch }}>{children}</Store.Provider>;
};
export const useStore = () => useContext(Store);
const updateWhiteList = (dispatch: React.Dispatch<Action>, whiteList: string[]) => {
dispatch({
type: "SET_WHITE_LIST",
payload: whiteList,
});
};
// Usage in a component:
const ExampleComponent = () => {
const { state, dispatch } = useStore();
const handleUpdateWhiteList = () => {
updateWhiteList(dispatch, ["asset1", "asset2"]);
};
return (
<div>
<button onClick={handleUpdateWhiteList}>Update WhiteList</button>
</div>
);
};

View file

@ -1,19 +0,0 @@
import CryptoJS from "crypto-js";
export default class ConnectKeyUtils {
static setConnectData(key, walletPort, extPass) {
const data = JSON.stringify({ token: key, port: walletPort });
localStorage.setItem("connectKey", CryptoJS.AES.encrypt(data, extPass).toString());
}
static getConnectKeyEncrypted() {
return localStorage.getItem("connectKey");
}
static getConnectData(password) {
const encrypted = localStorage.getItem("connectKey");
const decrypted = CryptoJS.AES.decrypt(encrypted, password).toString(CryptoJS.enc.Utf8);
const data = JSON.parse(decrypted);
return data;
}
}

View file

@ -0,0 +1,34 @@
import CryptoJS from "crypto-js";
interface ConnectData {
token: string;
port: string;
}
export default class ConnectKeyUtils {
static setConnectData(key: string, walletPort: string, extPass: string): void {
const data: ConnectData = { token: key, port: walletPort };
const encrypted = CryptoJS.AES.encrypt(JSON.stringify(data), extPass).toString();
localStorage.setItem("connectKey", encrypted);
}
static getConnectKeyEncrypted(): string | null {
return localStorage.getItem("connectKey");
}
static getConnectData(password: string): ConnectData | null {
const encrypted = localStorage.getItem("connectKey");
if (!encrypted) return null;
const decrypted = CryptoJS.AES.decrypt(encrypted, password).toString(CryptoJS.enc.Utf8);
if (!decrypted) return null;
try {
const data: ConnectData = JSON.parse(decrypted);
return data;
} catch (error) {
return null;
}
}
}

View file

@ -1,8 +0,0 @@
export const classNames = (cls, mods, additional = []) =>
[
cls,
...additional.filter(Boolean),
...Object.entries(mods)
.filter(([className, value]) => Boolean(value))
.map(([classNames]) => classNames),
].join(" ");

View file

@ -0,0 +1,15 @@
type Mods = Record<string, boolean | string | undefined>;
export const classNames = (
cls: string,
mods: Mods,
additional: (string | undefined)[] = []
): string => {
return [
cls,
...additional.filter(Boolean),
...Object.entries(mods)
.filter(([className, value]) => Boolean(value))
.map(([classNames]) => classNames),
].join(" ");
};

View file

@ -1,5 +1,5 @@
export default class Formatters {
static walletAddress(str) {
static walletAddress(str: string): string {
if (str.length > 20) {
if (window.innerWidth > 768) {
return (
@ -18,7 +18,7 @@ export default class Formatters {
return str;
}
static historyAmount(amount) {
static historyAmount(amount: number): string {
let str = amount.toString();
if (str.length > 10) {
return (

View file

@ -3,7 +3,23 @@ import Big from "big.js";
import Decimal from "decimal.js";
import sha256 from "sha256";
export async function fetchBackground(data) {
interface BackgroundResponse {
password: string;
}
interface ValidationResult {
valid: boolean;
error?: string;
}
export async function fetchBackground(data: {
method: string;
password?: string;
id?: number;
success?: boolean;
credentials?: { port: string };
alias?: string;
}): Promise<any> {
return new Promise((resolve, reject) => {
try {
chrome.runtime.sendMessage(data, function (response) {
@ -16,45 +32,43 @@ export async function fetchBackground(data) {
});
}
export const removeZeros = (amount, decimal_point = 12) => {
export const removeZeros = (amount: string | number, decimal_point: number = 12): string => {
const multiplier = new Big(10).pow(decimal_point);
const bigAmount = new Big(amount);
const fixedAmount = bigAmount.div(multiplier).toString();
return fixedAmount;
};
export const addZeros = (amount, decimal_point = 12) => {
export const addZeros = (amount: string | number, decimal_point: number = 12): Big => {
const multiplier = new Big(10).pow(decimal_point);
const bigAmount = new Big(amount);
const fixedAmount = bigAmount.times(multiplier);
return fixedAmount;
};
export const setPassword = (password) => {
export const setPassword = (password: string): void => {
localStorage.setItem("hash", sha256(password));
}
};
export const comparePasswords = (password) => {
export const comparePasswords = (password: string): boolean => {
const hash = localStorage.getItem("hash");
return hash === sha256(password);
}
};
export const passwordExists = () => {
export const passwordExists = (): boolean => {
return !!localStorage.getItem("hash");
}
};
export const getSessionPassword = async () => {
const sessionPass = (await fetchBackground({ method: "GET_PASSWORD" })).password;
return sessionPass;
}
export const getSessionPassword = async (): Promise<string> => {
const sessionPass = (await fetchBackground({ method: "GET_PASSWORD" })) as BackgroundResponse;
return sessionPass.password;
};
export const setSessionPassword = async (password) => {
await fetchBackground({ method: "SET_PASSWORD", password })
}
export function validateTokensInput(input, decimal_point) {
export const setSessionPassword = async (password: string): Promise<void> => {
await fetchBackground({ method: "SET_PASSWORD", password });
};
export function validateTokensInput(input: string | number, decimal_point: number): ValidationResult {
if (typeof input === 'number') {
input = input.toString();
}
@ -129,4 +143,4 @@ export function validateTokensInput(input, decimal_point) {
return {
valid: true
};
}
}

View file

@ -20,9 +20,17 @@ import JSONbig from "json-bigint";
const POPUP_HEIGHT = 630;
const POPUP_WIDTH = 370;
const ZANO_ID = "d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a";
const ZANO_ID =
"d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a";
async function getAsset(assetId) {
interface Asset {
asset_id: string;
ticker: string;
full_name: string;
decimal_point: number;
}
async function getAsset(assetId: string): Promise<Asset | undefined> {
if (assetId === ZANO_ID) {
return {
asset_id: ZANO_ID,
@ -42,19 +50,42 @@ async function getAsset(assetId) {
}
}
interface PopupRequest {
windowId: number;
finalizer: (data: unknown) => void;
[key: string]: unknown;
}
interface RequestResponse {
error?: string;
data?: unknown;
}
interface ErrorMessages {
console: string;
response: string;
reqNotFound: string;
}
class PopupRequestsMethods {
static onRequestCreate(requestType, request, sendResponse, reqParams) {
static onRequestCreate(
requestType: keyof typeof savedRequests,
request: { timeout?: number },
sendResponse: (response: RequestResponse) => void,
reqParams: PopupRequest
): void {
console.log("Creating request", reqParams);
openWindow().then((requestWindow) => {
const reqId = crypto.randomUUID();
const req = {
windowId: requestWindow.id,
finalizer: (data) => sendResponse(data),
...reqParams,
windowId: requestWindow.id,
finalizer: (data: unknown) => sendResponse(data as any),
};
allPopupIds.push(requestWindow.id);
savedRequests[requestType][reqId] = req;
allPopupIds.push(requestWindow.id as number);
(savedRequests[requestType][reqId] as any) = req;
if (typeof request.timeout === "number") {
setTimeout(() => {
@ -65,7 +96,10 @@ class PopupRequestsMethods {
});
}
static getRequestsList(requestType, sendResponse) {
static getRequestsList(
requestType: keyof typeof savedRequests,
sendResponse: (response: { data: PopupRequest[] }) => void
): void {
sendResponse({
data: Object.entries(savedRequests[requestType]).map(([id, req]) => ({
...req,
@ -76,18 +110,18 @@ class PopupRequestsMethods {
}
static onRequestFinalize(
requestType,
request,
sendResponse,
apiCallFunc,
errorMessages
) {
requestType: keyof typeof savedRequests,
request: { id: string; success: boolean },
sendResponse: (response: RequestResponse) => void,
apiCallFunc: (req: PopupRequest) => Promise<unknown>,
errorMessages: ErrorMessages
): void {
const reqId = request.id;
const success = request.success;
const req = savedRequests[requestType][reqId];
if (req) {
function finalize(data) {
function finalize(data: unknown) {
req.finalizer(data);
delete savedRequests[requestType][reqId];
chrome.windows.remove(req.windowId);
@ -109,18 +143,18 @@ class PopupRequestsMethods {
});
}
} else {
return sendResponse({ error: errorMessages.reqNotFound });
sendResponse({ error: errorMessages.reqNotFound });
}
}
}
chrome.windows.onBoundsChanged.addListener((window) => {
if (
allPopupIds.includes(window.id) &&
allPopupIds.includes(window.id as number) &&
window.width !== POPUP_WIDTH &&
window.height !== POPUP_HEIGHT
) {
chrome.windows.update(window.id, {
chrome.windows.update(window.id as number, {
width: POPUP_WIDTH,
height: POPUP_HEIGHT,
});
@ -132,23 +166,40 @@ chrome.runtime.onStartup.addListener(() => {
console.log("Background script loaded on startup");
});
const defaultCredentials = {
interface Credentials {
port: number;
token?: string;
}
const defaultCredentials: Credentials = {
port: 11211,
};
export let apiCredentials = JSON.parse(JSON.stringify(defaultCredentials));
export let apiCredentials: Credentials = { ...defaultCredentials };
let pendingTx = null;
interface pendingTxTypes {
assetId: string,
amount: string,
destinationAddress: string | undefined,
destinationChainId: string | undefined,
}
const defaultUserData = { password: undefined };
let pendingTx: pendingTxTypes | null = null;
async function setUserData(state) {
interface UserData {
password?: string;
apiCredentials?: Credentials;
}
const defaultUserData: UserData = { password: undefined };
async function setUserData(state: UserData): Promise<void> {
await new Promise((resolve) => {
chrome.storage.local.set({ userData: state }, resolve);
chrome.storage.local.set({ userData: state }, resolve as (() => void));
});
}
async function getUserData() {
async function getUserData(): Promise<UserData> {
return new Promise((resolve) => {
chrome.storage.local.get("userData", (result) => {
resolve(result.userData || defaultUserData);
@ -156,41 +207,47 @@ async function getUserData() {
});
}
async function updateUserData(newData) {
async function updateUserData(newData: Partial<UserData>): Promise<void> {
const currentData = await getUserData();
return setUserData({ ...currentData, ...newData });
}
async function recoverApiCredentials() {
async function recoverApiCredentials(): Promise<void> {
apiCredentials = (await getUserData()).apiCredentials || defaultCredentials;
}
chrome.runtime.onStartup.addListener(() => {
chrome.storage.local.remove("userData", function () {
chrome.storage.local.remove("userData", () => {
console.log("State cleared on browser startup");
});
});
const signReqFinalizers = {};
const signReqs = [];
interface SignReqFinalizer {
[key: string]: (data: unknown) => void;
}
const savedRequests = {
const signReqFinalizers: SignReqFinalizer = {};
const signReqs: unknown[] = [];
const savedRequests: Record<
"IONIC_SWAP" | "ACCEPT_IONIC_SWAP" | "CREATE_ALIAS" | "TRANSFER",
Record<string, PopupRequest>
> = {
IONIC_SWAP: {},
ACCEPT_IONIC_SWAP: {},
CREATE_ALIAS: {},
TRANSFER: {}
TRANSFER: {},
};
const allPopupIds = [];
const allPopupIds: number[] = [];
// eslint-disable-next-line no-undef
chrome.storage.local.get("pendingTx", (result) => {
if (result.pendingTx) {
pendingTx = result.pendingTx;
}
});
function openWindow() {
function openWindow(): Promise<chrome.windows.Window> {
return chrome.windows.create({
url: chrome.runtime.getURL("index.html"),
type: "popup",
@ -218,15 +275,60 @@ const SELF_ONLY_REQUESTS = [
"PING_WALLET",
"SET_ACTIVE_WALLET",
"GET_WALLETS",
"FINALIZE_TRANSFER_REQUEST"
"FINALIZE_TRANSFER_REQUEST",
];
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
processRequest(request, sender, sendResponse);
processRequest(request, sender as any, sendResponse);
return true;
});
async function processRequest(request, sender, sendResponse) {
interface RequestType {
method: string;
credentials: Object;
id: string;
assetId: string;
destination: string;
amount: string;
decimalPoint: string;
success: boolean;
destinationAssetID: string;
currentAssetID: string;
currentAsset: Asset;
destinationAsset: Asset;
hex_raw_proposal?: string;
alias?: string;
sender?: string;
transfer?: any;
swapProposal?: any;
password?: string;
key?: string;
aliasDetails?: any;
signReqs?: any[];
windowId?: number;
message?: string;
timeout?: number;
destinationChainId?: string;
destinationAddress?: string;
receivingAsset?: any;
sendingAsset?: any;
asset?: Asset;
}
interface Sender {
id: string;
name?: string;
email?: string;
phoneNumber?: string;
address?: string;
[key: string]: any;
}
interface SendResponse {
(response: any): void;
}
async function processRequest(request: RequestType, sender: Sender, sendResponse: SendResponse) {
const isFromExtensionFrontend =
sender.url && sender.url.includes(chrome.runtime.getURL("/"));
@ -300,7 +402,7 @@ async function processRequest(request, sender, sendResponse) {
break;
case "GET_WALLET_DATA":
getWalletData(request.id)
getWalletData() // removed request.id
.then((data) => {
sendResponse({ data });
})
@ -371,14 +473,14 @@ async function processRequest(request, sender, sendResponse) {
"IONIC_SWAP",
request,
sendResponse,
{ swap: request }
{ swap: request } as any
);
break;
}
case "GET_TRANSFER_REQUEST": {
PopupRequestsMethods.getRequestsList("TRANSFER", sendResponse);
break
break;
}
case "TRANSFER": {
@ -386,39 +488,45 @@ async function processRequest(request, sender, sendResponse) {
const asset = await getAsset(request.assetId);
const walletData = await getWalletData();
const { address } = walletData;
request.asset = asset || await getAsset(ZANO_ID);
request.sender = address || '';
} catch (e) {
return sendResponse({error: e.message})
request.asset = asset || (await getAsset(ZANO_ID));
request.sender = address || "";
} catch (e: unknown) {
if (e instanceof Error) {
return sendResponse({ error: e.message });
} else {
return sendResponse({ error: 'Unknown error occurred' });
}
}
PopupRequestsMethods.onRequestCreate(
"TRANSFER",
request,
sendResponse,
{ transfer: request }
);
break
PopupRequestsMethods.onRequestCreate("TRANSFER", request, sendResponse, {
transfer: request,
} as any);
break;
}
case "FINALIZE_TRANSFER_REQUEST": {
PopupRequestsMethods
.onRequestFinalize("TRANSFER",
request,
sendResponse,
(req) => {
const transferData = req.transfer;
const {assetId, destination, amount, asset } = transferData;
return transfer(assetId, destination, amount, asset?.decimal_point || 12);
},
{
console: "Error transfer:",
response: "An error occurred while sending transfer",
reqNotFound: "transfer request not found",
})
break
PopupRequestsMethods.onRequestFinalize(
"TRANSFER",
request,
sendResponse,
(req) => {
const transferData: any = req.transfer;
const { assetId, destination, amount, asset } = transferData;
return transfer(
assetId,
destination,
amount,
asset?.decimal_point || 12
);
},
{
console: "Error transfer:",
response: "An error occurred while sending transfer",
reqNotFound: "transfer request not found",
}
);
break;
}
case "GET_ACCEPT_IONIC_SWAP_REQUESTS":
@ -499,7 +607,7 @@ async function processRequest(request, sender, sendResponse) {
"ACCEPT_IONIC_SWAP",
request,
sendResponse,
request
request as any
);
break;
}
@ -568,7 +676,7 @@ async function processRequest(request, sender, sendResponse) {
}
case "GET_ALIAS_DETAILS": {
getAliasDetails(request.alias)
getAliasDetails(String(request.alias))
.then((res) => sendResponse(res))
.catch(() => sendResponse({ error: "Internal error" }));
break;
@ -582,10 +690,10 @@ async function processRequest(request, sender, sendResponse) {
case "FINALIZE_MESSAGE_SIGN": {
const reqId = request.id;
const success = request.success;
const signReq = signReqs.find((req) => req.id === reqId);
const signReq: any = signReqs.find((req: any) => req.id === reqId);
if (signReq && signReqFinalizers[reqId]) {
function finalize(data) {
function finalize(data: any) {
signReqFinalizers[reqId](data);
signReqs.splice(signReqs.indexOf(signReq), 1);
delete signReqFinalizers[reqId];
@ -630,7 +738,7 @@ async function processRequest(request, sender, sendResponse) {
sendResponse(result);
};
allPopupIds.push(requestWindow.id);
allPopupIds.push(Number(requestWindow.id));
signReqs.push({
id: signReqId,
@ -641,7 +749,7 @@ async function processRequest(request, sender, sendResponse) {
if (typeof request.timeout === "number") {
setTimeout(() => {
const signReqIndex = signReqs.findIndex(
(req) => req.id === signReqId
(req: any) => req.id === signReqId
);
if (signReqIndex === -1) {
@ -712,7 +820,7 @@ async function processRequest(request, sender, sendResponse) {
"CREATE_ALIAS",
request,
sendResponse,
{ alias: request.alias }
({ alias: request.alias } as any)
);
break;
}
@ -721,4 +829,4 @@ async function processRequest(request, sender, sendResponse) {
console.error("Unknown message method:", request.method);
sendResponse({ error: `Unknown method: ${request.method}` });
}
}
}

View file

@ -5,7 +5,14 @@ import { Buffer } from "buffer";
import JSONbig from "json-bigint";
// window.Buffer = Buffer;
function createJWSToken(payload, secrete_str) {
interface JWTPayload {
body_hash: string,
user: string,
salt: string,
exp: number
}
function createJWSToken(payload: JWTPayload, secretStr: string): string {
const header = { alg: "HS256", typ: "JWT" };
const encodedHeader = Buffer.from(JSON.stringify(header))
.toString("base64")
@ -15,7 +22,7 @@ function createJWSToken(payload, secrete_str) {
.replace(/=/g, "");
const signature = forge.hmac.create();
signature.start("sha256", secrete_str);
signature.start("sha256", secretStr);
signature.update(`${encodedHeader}.${encodedPayload}`);
const encodedSignature = forge.util
.encode64(signature.digest().getBytes())
@ -24,13 +31,13 @@ function createJWSToken(payload, secrete_str) {
return `${encodedHeader}.${encodedPayload}.${encodedSignature}`;
}
function generateRandomString(length) {
function generateRandomString(length: number) {
const bytes = forge.random.getBytesSync(Math.ceil(length / 2));
const hexString = forge.util.bytesToHex(bytes);
return hexString.substring(0, length);
}
function generateAccessToken(httpBody) {
function generateAccessToken(httpBody: string) {
if (!apiCredentials?.token) {
throw new Error("No API credentials found, extension is not connected");
}
@ -51,16 +58,23 @@ function generateAccessToken(httpBody) {
return createJWSToken(payload, apiCredentials?.token);
}
export const fetchData = async (method, params = {}) => {
const httpBody = JSON.stringify({
interface fetchDataProps {
offset: number,
update_provision_info: boolean,
exclude_mining_txs: boolean,
count: number,
order: string,
exclude_unconfirmed: boolean,
}
export const fetchData = async (method: string, params: fetchDataProps | {} = {}): Promise<Response> => {
const httpBody: string = JSON.stringify({
jsonrpc: "2.0",
id: "0",
method,
params,
});
// console.log("fetchData:", httpBody);
return fetch(`http://localhost:${apiCredentials.port}/json_rpc`, {
method: "POST",
headers: {
@ -93,7 +107,7 @@ const fetchTxData = async () => {
}
};
export const getAlias = async (address) => {
export const getAlias = async (address:string) => {
const response = await fetchData("get_alias_by_address", address);
const data = await response.json();
if (data.result?.status === "OK") {
@ -103,7 +117,7 @@ export const getAlias = async (address) => {
}
};
export const getAliasDetails = async (alias) => {
export const getAliasDetails = async (alias: string) => {
const response = await fetchData("get_alias_details", { alias });
const data = await response.json();
if (data.result.status === "OK") {
@ -125,10 +139,10 @@ export const getWallets = async () => {
// console.log("wallets:", data.result.wallets);
const wallets = await Promise.all(
data.result.wallets.map(async (wallet) => {
data.result.wallets.map(async (wallet: any) => {
const alias = await getAlias(wallet.wi.address);
const balance = wallet.wi.balances.find(
(asset) =>
(asset: any) =>
asset.asset_info.asset_id ===
"d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a"
).total;
@ -163,7 +177,7 @@ export const getWalletData = async () => {
const balanceParsed = JSONbig.parse(await balanceResponse.text());
const assets = balanceParsed.result.balances
.map((asset) => ({
.map((asset: any) => ({
name: asset.asset_info.full_name,
ticker: asset.asset_info.ticker,
assetId: asset.asset_info.asset_id,
@ -174,7 +188,7 @@ export const getWalletData = async () => {
asset.asset_info.decimal_point
),
}))
.sort((a, b) => {
.sort((a: any, b:any) => {
if (
a.assetId ===
"d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a"
@ -189,13 +203,13 @@ export const getWalletData = async () => {
return 0;
});
function getAssetDecimalPoint(assetId) {
return assets.find((asset) => asset.assetId === assetId)?.decimalPoint;
function getAssetDecimalPoint(assetId: any) {
return assets.find((asset:any) => asset.assetId === assetId)?.decimalPoint;
}
const balance = removeZeros(
balanceParsed.result.balances.find(
(asset) =>
(asset: any) =>
asset.asset_info.asset_id ===
"d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a"
).total
@ -206,8 +220,8 @@ export const getWalletData = async () => {
if (txData) {
transactions = txData
.filter((tx) => !tx.is_service)
.map((tx) => ({
.filter((tx:any) => !tx.is_service)
.map((tx:any) => ({
isConfirmed: tx.height === 0 ? false : true,
txHash: tx.tx_hash,
blobSize: tx.tx_blob_size,
@ -218,9 +232,9 @@ export const getWalletData = async () => {
fee: removeZeros(tx.fee),
addresses: tx.remote_addresses,
isInitiator: !!tx.employed_entries?.spent?.some?.(
(e) => e?.index === 0
(e:any) => e?.index === 0
),
transfers: tx.subtransfers.map((transfer) => ({
transfers: tx.subtransfers.map((transfer:any) => ({
amount: removeZeros(
transfer.amount,
getAssetDecimalPoint(transfer.asset_id) || 12
@ -237,7 +251,7 @@ export const getWalletData = async () => {
return { address, alias, balance, transactions, assets };
};
export const ionicSwap = async (swapParams) => {
export const ionicSwap = async (swapParams:any) => {
const response = await fetchData("ionic_swap_generate_proposal", {
proposal: {
to_initiator: [
@ -273,7 +287,7 @@ export const ionicSwap = async (swapParams) => {
return data;
};
export const ionicSwapAccept = async (swapParams) => {
export const ionicSwapAccept = async (swapParams:any) => {
console.log(swapParams.hex_raw_proposal);
const response = await fetchData("ionic_swap_accept_proposal", {
@ -288,7 +302,7 @@ export const ionicSwapAccept = async (swapParams) => {
return data;
};
export const createAlias = async ({ alias, address }) => {
export const createAlias = async ({ alias, address }: any) => {
const response = await fetchData("register_alias", {
al: {
address: address,
@ -301,9 +315,9 @@ export const createAlias = async ({ alias, address }) => {
export const transfer = async (
assetId = "d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a",
destination,
amount,
decimalPoint
destination: any,
amount: any,
decimalPoint: any
) => {
const destinations = [
{
@ -333,9 +347,9 @@ export const transfer = async (
// TODO: move bridge address to the config
export const burnBridge = async (
assetId = "d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a",
amount,
destinationAddress,
destinationChainId
amount: any,
destinationAddress: any,
destinationChainId: any
) => {
const bodyData = {
service_id: "B",
@ -381,7 +395,7 @@ export const burnBridge = async (
return data;
};
export const signMessage = async (message) => {
export const signMessage = async (message: any) => {
const base64 = Buffer.from(message).toString("base64");
const signRequest = {
@ -409,7 +423,7 @@ export const createConnectKey = async () => {
).then((r) => r.json());
};
export const validateConnectKey = async (key) => {
export const validateConnectKey = async (key: any) => {
return await fetch(
`http://localhost:${apiCredentials.port}/validate-connection-key`,
{
@ -422,7 +436,7 @@ export const validateConnectKey = async (key) => {
).then((r) => r.json());
};
export const getSwapProposalInfo = async (hex) => {
export const getSwapProposalInfo = async (hex: any) => {
const response = await fetchData("ionic_swap_get_proposal_info", {
hex_raw_proposal: hex,
});
@ -445,7 +459,7 @@ export async function getWhiteList() {
if (
fetchedWhiteList.every(
(e) =>
(e:any) =>
e.asset_id !==
"d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a"
)
@ -462,7 +476,7 @@ export async function getWhiteList() {
return fetchedWhiteList;
}
export async function getAssetInfo(assetId) {
export async function getAssetInfo(assetId: any) {
const response = await fetchData("get_asset_info", { asset_id: assetId });
if (!response.ok) {

View file

@ -1,11 +1,24 @@
async function fetchData(data) {
interface DocumentEventMap {
"zano_request": CustomEvent<ZanoRequestData>;
}
interface ZanoRequestData {
method: string;
listenerID: string;
timeout?: number | null;
[key: string]: string | number | boolean | null | undefined;
}
interface ZanoResponse {
error?: string;
[key: string]: string | number | boolean | null | undefined;
}
async function fetchData(data: ZanoRequestData): Promise<ZanoResponse> {
return new Promise((resolve, reject) => {
try {
// eslint-disable-next-line no-undef
chrome.runtime.sendMessage(data, (response) => {
// eslint-disable-next-line no-undef
chrome.runtime.sendMessage(data, (response: ZanoResponse) => {
if (chrome.runtime.lastError) {
// eslint-disable-next-line no-undef
reject(chrome.runtime.lastError);
} else {
resolve(response);
@ -18,7 +31,7 @@ async function fetchData(data) {
});
}
document.addEventListener("zano_request", async (e) => {
document.addEventListener("zano_request", async (e: CustomEvent<ZanoRequestData>) => {
const data = e.detail;
try {
@ -31,10 +44,9 @@ document.addEventListener("zano_request", async (e) => {
);
} catch (error) {
console.error(`Error while processing zano_request:`, error);
// Dispatch an event with the error
document.dispatchEvent(
new CustomEvent(`zano_response_${data.listenerID}`, {
detail: { error: error.message },
detail: { error: error instanceof Error ? error.message : String(error) },
})
);
}

View file

@ -1,9 +1,9 @@
class Zano {
async request(method, params, timeoutParam) {
async request(method: string, params: Record<string, any>, timeoutParam?: number): Promise<any> {
function getRandonString(length) {
let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
let charLength = chars.length;
function getRandonString(length: number): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
const charLength = chars.length;
let result = '';
for (let i = 0; i < length; i++) {
@ -13,28 +13,27 @@ class Zano {
return result;
}
const listenerID = getRandonString(16);
const timeoutMs = typeof timeoutParam === "number" ? timeoutParam : null;
const timeoutMs: number | null = typeof timeoutParam === "number" ? timeoutParam : null;
return new Promise((resolve, reject) => {
const timeout = timeoutMs !== null ? (
setTimeout(() => {
reject('Request timeout exceeded');
document.removeEventListener(`zano_response_${listenerID}`, handleResponse);
document.removeEventListener(`zano_response_${listenerID}`, handleResponse as EventListener);
}, timeoutMs)
) : undefined;
function handleResponse(e) {
document.removeEventListener(`zano_response_${listenerID}`, handleResponse);
function handleResponse(e: CustomEvent) {
document.removeEventListener(`zano_response_${listenerID}`, handleResponse as EventListener);
if (timeout) {
clearTimeout(timeout);
}
resolve(e.detail);
}
document.addEventListener(`zano_response_${listenerID}`, handleResponse);
document.addEventListener(`zano_response_${listenerID}`, handleResponse as EventListener);
document.dispatchEvent(new CustomEvent('zano_request', {
detail: {
@ -49,4 +48,4 @@ class Zano {
}
}
window.zano = new Zano();
window.zano = new Zano();

29
src/global.d.ts vendored Normal file
View file

@ -0,0 +1,29 @@
interface Window {
zano: Zano;
}
declare module "*.scss" {
const content: { [className: string]: string };
export default content;
}
declare module "*.svg" {
import React from "react";
const content: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
export default content;
}
declare module "*.png" {
const value: string;
export default value;
}
declare module "*.jpg" {
const value: string;
export default value;
}
declare module "*.jpeg" {
const value: string;
export default value;
}

View file

@ -3,7 +3,7 @@ import ReactDOM from "react-dom/client";
import App from "./app/App";
import { StoreProvider } from "./app/store/store-reducer";
const root = ReactDOM.createRoot(document.getElementById("root"));
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
root.render(
<React.StrictMode>
<StoreProvider>

16
tsconfig.json Normal file
View file

@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es6",
"module": "es6",
"moduleResolution": "node",
"jsx": "react",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"allowJs": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}

View file

@ -54,9 +54,14 @@ module.exports = {
},
],
},
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
use: "ts-loader",
},
],
},
resolve: {
extensions: ["*", ".js", ".jsx"],
extensions: ["*", ".js", ".jsx", ".ts", ".tsx"],
},
};

View file

@ -5,7 +5,7 @@ const HtmlWebpackPlugin = require("html-webpack-plugin");
const common = require("./webpack.common.js");
const appConfig = {
entry: "./src/index.js",
entry: "./src/index.tsx",
output: {
path: path.resolve(__dirname, "build"),
filename: "static/js/app.bundle.js",
@ -20,7 +20,7 @@ const appConfig = {
};
const backgroundConfig = {
entry: "./src/background/background.js",
entry: "./src/background/background.ts",
output: {
path: path.resolve(__dirname, "build/static/js"),
filename: "background.bundle.js",
@ -28,7 +28,7 @@ const backgroundConfig = {
};
const contentConfig = {
entry: "./src/content/content.js",
entry: "./src/content/content.ts",
output: {
path: path.resolve(__dirname, "build/static/js"),
filename: "content.bundle.js",
@ -36,7 +36,7 @@ const contentConfig = {
};
const injectConfig = {
entry: "./src/content/inject.js",
entry: "./src/content/inject.ts",
output: {
path: path.resolve(__dirname, "build/static/js"),
filename: "inject.bundle.js",