diff --git a/package-lock.json b/package-lock.json
index 5ad262f..94634b6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -55,6 +55,7 @@
"react-infinite-scroller": "^1.2.6",
"react-intersection-observer": "^9.13.0",
"react-json-view-lite": "^2.0.1",
+ "react-loader-spinner": "^6.1.6",
"react-qr-code": "^2.0.15",
"react-quill": "^2.0.0",
"react-redux": "^9.1.2",
@@ -2887,6 +2888,11 @@
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
"dev": true
},
+ "node_modules/@types/stylis": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz",
+ "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw=="
+ },
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
@@ -3593,6 +3599,14 @@
"node": ">=6"
}
},
+ "node_modules/camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/caniuse-lite": {
"version": "1.0.30001599",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz",
@@ -4027,6 +4041,24 @@
"node": ">= 8"
}
},
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
"node_modules/css.escape": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
@@ -6372,7 +6404,6 @@
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
- "devOptional": true,
"funding": [
{
"type": "github",
@@ -9106,10 +9137,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.37",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.37.tgz",
- "integrity": "sha512-7iB/v/r7Woof0glKLH8b1SPHrsX7uhdO+Geb41QpF/+mWZHU3uxxSlN+UXGVit1PawOYDToO+AbZzhBzWRDwbQ==",
- "devOptional": true,
+ "version": "8.4.38",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
+ "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
"funding": [
{
"type": "opencollective",
@@ -9133,6 +9163,11 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -9631,6 +9666,27 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
+ "node_modules/react-loader-spinner": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/react-loader-spinner/-/react-loader-spinner-6.1.6.tgz",
+ "integrity": "sha512-x5h1Jcit7Qn03MuKlrWcMG9o12cp9SNDVHVJTNRi9TgtGPKcjKiXkou4NRfLAtXaFB3+Z8yZsVzONmPzhv2ErA==",
+ "dependencies": {
+ "react-is": "^18.2.0",
+ "styled-components": "^6.1.2"
+ },
+ "engines": {
+ "node": ">= 12"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/react-loader-spinner/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
+ },
"node_modules/react-qr-code": {
"version": "2.0.15",
"resolved": "https://registry.npmjs.org/react-qr-code/-/react-qr-code-2.0.15.tgz",
@@ -10258,6 +10314,11 @@
"node": ">= 0.4"
}
},
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -10358,7 +10419,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
- "devOptional": true,
"engines": {
"node": ">=0.10.0"
}
@@ -10491,6 +10551,38 @@
"devOptional": true,
"license": "MIT"
},
+ "node_modules/styled-components": {
+ "version": "6.1.13",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.13.tgz",
+ "integrity": "sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==",
+ "dependencies": {
+ "@emotion/is-prop-valid": "1.2.2",
+ "@emotion/unitless": "0.8.1",
+ "@types/stylis": "4.2.5",
+ "css-to-react-native": "3.2.0",
+ "csstype": "3.1.3",
+ "postcss": "8.4.38",
+ "shallowequal": "1.1.0",
+ "stylis": "4.3.2",
+ "tslib": "2.6.2"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/styled-components"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0",
+ "react-dom": ">= 16.8.0"
+ }
+ },
+ "node_modules/styled-components/node_modules/stylis": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz",
+ "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg=="
+ },
"node_modules/stylis": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
diff --git a/package.json b/package.json
index 7d4c08e..cf326b5 100644
--- a/package.json
+++ b/package.json
@@ -44,6 +44,7 @@
"file-saver": "^2.0.5",
"html-to-text": "^9.0.5",
"jssha": "3.3.1",
+ "lit": "^3.2.1",
"lodash": "^4.17.21",
"mime": "^4.0.4",
"moment": "^2.30.1",
@@ -58,6 +59,7 @@
"react-infinite-scroller": "^1.2.6",
"react-intersection-observer": "^9.13.0",
"react-json-view-lite": "^2.0.1",
+ "react-loader-spinner": "^6.1.6",
"react-qr-code": "^2.0.15",
"react-quill": "^2.0.0",
"react-redux": "^9.1.2",
@@ -65,7 +67,6 @@
"react-virtuoso": "^4.10.4",
"recoil": "^0.7.7",
"short-unique-id": "^5.2.0",
- "lit": "^3.2.1",
"slate": "^0.103.0",
"slate-react": "^0.109.0",
"tiptap-extension-resize-image": "^1.1.8",
diff --git a/src/App.tsx b/src/App.tsx
index 11b5341..c0043de 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -46,6 +46,7 @@ import CloseIcon from "@mui/icons-material/Close";
import { JsonView, allExpanded, darkStyles } from 'react-json-view-lite';
import 'react-json-view-lite/dist/index.css';
import HelpIcon from '@mui/icons-material/Help';
+import EngineeringIcon from '@mui/icons-material/Engineering';
import {
createAccount,
@@ -120,6 +121,8 @@ import { Wallets } from "./Wallets";
import './utils/seedPhrase/RandomSentenceGenerator';
import { test } from "vitest";
import { useHandleUserInfo } from "./components/Group/useHandleUserInfo";
+import { Minting } from "./components/Minting/Minting";
+import { isRunningGateway } from "./qortalRequests";
type extStates =
| "not-authenticated"
@@ -329,6 +332,8 @@ function App() {
const {downloadResource} = useFetchResources()
const [showSeed, setShowSeed] = useState(false)
const [creationStep, setCreationStep] = useState(1)
+ const [isOpenMinting, setIsOpenMinting] = useState(false)
+
const setIsDisabledEditorEnter = useSetRecoilState(isDisabledEditorEnterAtom)
const {
isShow: isShowInfo,
@@ -1651,6 +1656,54 @@ function App() {
alignItems: 'center'
}}
>
+ {extState === "authenticated" && isMainWindow && (
+
+
+
+ )}
+
+ {
+ try {
+ const res = await isRunningGateway()
+ if(res) throw new Error('Cannot view minting details on the gateway')
+ setIsOpenMinting(true)
+
+ } catch (error) {
+ setOpenSnack(true)
+ setInfoSnack({
+ type: 'error',
+ message: error?.message
+ })
+ }
+ }}>
+
+
+
+
+
{(desktopViewMode === "apps" || desktopViewMode === "home") && (
{
if(desktopViewMode === "apps"){
@@ -1755,17 +1808,7 @@ function App() {
{!isMobile && renderProfile()}
-
-
-
+
)}
{isOpenSendQort && isMainWindow && (
@@ -3306,6 +3349,9 @@ function App() {
}} />
)}
+ {isOpenMinting && (
+
+ )}
);
}
diff --git a/src/background-cases.ts b/src/background-cases.ts
new file mode 100644
index 0000000..c1b64c2
--- /dev/null
+++ b/src/background-cases.ts
@@ -0,0 +1,82 @@
+import { getKeyPair, getLastRef, processTransactionVersion2 } from "./background";
+import Base58 from "./deps/Base58";
+import { createTransaction } from "./transactions/transactions";
+
+
+export async function createRewardShareCase(data) {
+ const {recipientPublicKey} = data;
+ const resKeyPair = await getKeyPair();
+ const parsedData = JSON.parse(resKeyPair);
+ const uint8PrivateKey = Base58.decode(parsedData.privateKey);
+ const uint8PublicKey = Base58.decode(parsedData.publicKey);
+ const keyPair = {
+ privateKey: uint8PrivateKey,
+ publicKey: uint8PublicKey,
+ };
+ let lastRef = await getLastRef();
+
+ const tx = await createTransaction(38, keyPair, {
+ recipientPublicKey,
+ percentageShare: 0,
+ lastReference: lastRef,
+ });
+
+ const signedBytes = Base58.encode(tx.signedBytes);
+
+ const res = await processTransactionVersion2(signedBytes);
+ if (!res?.signature)
+ throw new Error("Transaction was not able to be processed");
+ return res
+
+ }
+
+ export async function removeRewardShareCase(data) {
+
+ const {rewardShareKeyPairPublicKey, recipient, percentageShare} = data;
+ const resKeyPair = await getKeyPair();
+ const parsedData = JSON.parse(resKeyPair);
+ const uint8PrivateKey = Base58.decode(parsedData.privateKey);
+ const uint8PublicKey = Base58.decode(parsedData.publicKey);
+ const keyPair = {
+ privateKey: uint8PrivateKey,
+ publicKey: uint8PublicKey,
+ };
+ let lastRef = await getLastRef();
+
+ const tx = await createTransaction(381, keyPair, {
+ rewardShareKeyPairPublicKey,
+ recipient,
+ percentageShare,
+ lastReference: lastRef,
+ });
+
+ const signedBytes = Base58.encode(tx.signedBytes);
+
+ const res = await processTransactionVersion2(signedBytes);
+ if (!res?.signature)
+ throw new Error("Transaction was not able to be processed");
+ return res
+
+ }
+
+
+ export async function getRewardSharePrivateKeyCase(data) {
+ const {recipientPublicKey} = data
+ const resKeyPair = await getKeyPair();
+ const parsedData = JSON.parse(resKeyPair);
+ const uint8PrivateKey = Base58.decode(parsedData.privateKey);
+ const uint8PublicKey = Base58.decode(parsedData.publicKey);
+ const keyPair = {
+ privateKey: uint8PrivateKey,
+ publicKey: uint8PublicKey,
+ };
+ let lastRef = await getLastRef();
+
+ const tx = await createTransaction(38, keyPair, {
+ recipientPublicKey,
+ percentageShare: 0,
+ lastReference: lastRef,
+ });
+
+ return tx?._base58RewardShareSeed
+ }
diff --git a/src/background.ts b/src/background.ts
index a772298..32dbe0f 100644
--- a/src/background.ts
+++ b/src/background.ts
@@ -32,6 +32,7 @@ import { Sha256 } from "asmcrypto.js";
import { TradeBotRespondMultipleRequest } from "./transactions/TradeBotRespondMultipleRequest";
import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from "./constants/resourceTypes";
import TradeBotRespondRequest from './transactions/TradeBotRespondRequest';
+import { createRewardShareCase, getRewardSharePrivateKeyCase, removeRewardShareCase } from './background-cases';
@@ -1258,7 +1259,7 @@ export const sendQortFee = async (): Promise => {
return qortFee;
};
-async function getNameOrAddress(receiver) {
+export async function getNameOrAddress(receiver) {
try {
const isAddress = validateAddress(receiver);
if (isAddress) {
@@ -4633,6 +4634,48 @@ chrome?.runtime?.onMessage.addListener((request, sender, sendResponse) => {
break;
}
+ case "createRewardShare": {
+ const {
+ recipientPublicKey
+ } = request.payload;
+ createRewardShareCase({recipientPublicKey})
+ .then((res) => {
+ sendResponse(res);
+ })
+ .catch((error) => {
+ sendResponse({ error: error?.message });
+ });
+
+ break;
+ }
+ case "getRewardSharePrivateKey": {
+ const {
+ recipientPublicKey
+ } = request.payload;
+ getRewardSharePrivateKeyCase({recipientPublicKey})
+ .then((res) => {
+ sendResponse(res);
+ })
+ .catch((error) => {
+ sendResponse({ error: error?.message });
+ });
+
+ break;
+ }
+ case "removeRewardShare": {
+ const {
+ rewardShareKeyPairPublicKey, recipient, percentageShare
+ } = request.payload;
+ removeRewardShareCase({rewardShareKeyPairPublicKey, recipient, percentageShare})
+ .then((res) => {
+ sendResponse(res);
+ })
+ .catch((error) => {
+ sendResponse({ error: error?.message });
+ });
+
+ break;
+ }
case "addEnteredQmailTimestamp": {
addEnteredQmailTimestamp()
diff --git a/src/components/Minting/Minting.tsx b/src/components/Minting/Minting.tsx
new file mode 100644
index 0000000..87e0852
--- /dev/null
+++ b/src/components/Minting/Minting.tsx
@@ -0,0 +1,1198 @@
+import {
+ Alert,
+ Box,
+ Button,
+ Card,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ Divider,
+ IconButton,
+ InputBase,
+ InputLabel,
+ Snackbar,
+ Typography,
+} from "@mui/material";
+import React, {
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+ useState,
+} from "react";
+import CloseIcon from "@mui/icons-material/Close";
+import { MyContext, getBaseApiReact } from "../../App";
+import {
+ executeEvent,
+ subscribeToEvent,
+ unsubscribeFromEvent,
+} from "../../utils/events";
+import { getFee, getNameOrAddress } from "../../background";
+import CopyToClipboard from "react-copy-to-clipboard";
+import { AddressBox } from "../../App-styles";
+import { Spacer } from "../../common/Spacer";
+import Copy from "../../assets/svgs/Copy.svg";
+import { Loader } from "../Loader";
+import { FidgetSpinner } from "react-loader-spinner";
+import { useModal } from "../../common/useModal";
+
+export const Minting = ({
+ setIsOpenMinting,
+ myAddress,
+ groups,
+ show,
+ setTxList,
+ txList,
+}) => {
+ const [mintingAccounts, setMintingAccounts] = useState([]);
+ const [accountInfo, setAccountInfo] = useState(null);
+ const [rewardSharePublicKey, setRewardSharePublicKey] = useState("");
+ const [mintingKey, setMintingKey] = useState("");
+ const [rewardsharekey, setRewardsharekey] = useState("");
+ const [rewardShares, setRewardShares] = useState([]);
+ const [nodeInfos, setNodeInfos] = useState({});
+ const [openSnack, setOpenSnack] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+ const { isShow, onCancel, onOk, show: showKey, message } = useModal();
+ const [info, setInfo] = useState(null);
+ const [names, setNames] = useState({});
+ const [accountInfos, setAccountInfos] = useState({});
+
+
+
+
+ const isPartOfMintingGroup = useMemo(() => {
+ if (groups?.length === 0) return false;
+ return !!groups?.find((item) => item?.groupId?.toString() === "694");
+ }, [groups]);
+ const getMintingAccounts = useCallback(async () => {
+ try {
+ const url = `${getBaseApiReact()}/admin/mintingaccounts`;
+ const response = await fetch(url);
+ if (!response.ok) {
+ throw new Error("network error");
+ }
+ const data = await response.json();
+ setMintingAccounts(data);
+ } catch (error) {}
+ }, []);
+
+ const accountIsMinting = useMemo(() => {
+ return !!mintingAccounts?.find(
+ (item) => item?.recipientAccount === myAddress
+ );
+ }, [mintingAccounts, myAddress]);
+
+ const getName = async (address) => {
+ try {
+ const response = await fetch(
+ `${getBaseApiReact()}/names/address/${address}`
+ );
+ const nameData = await response.json();
+ if (nameData?.length > 0) {
+ setNames((prev) => {
+ return {
+ ...prev,
+ [address]: nameData[0].name,
+ };
+ });
+ } else {
+ setNames((prev) => {
+ return {
+ ...prev,
+ [address]: null,
+ };
+ });
+ }
+ } catch (error) {
+ // error
+ }
+ };
+
+ const getAccountInfo = async (address: string, others?: boolean) => {
+ try {
+ if (!others) {
+ setIsLoading(true);
+ }
+ const url = `${getBaseApiReact()}/addresses/${address}`;
+ const response = await fetch(url);
+ if (!response.ok) {
+ throw new Error("network error");
+ }
+ const data = await response.json();
+ if (others) {
+ setAccountInfos((prev) => {
+ return {
+ ...prev,
+ [address]: data,
+ };
+ });
+ } else {
+ setAccountInfo(data);
+ }
+ } catch (error) {
+ } finally {
+ if (!others) {
+ setIsLoading(false);
+ }
+ }
+ };
+
+ const refreshRewardShare = () => {
+ if (!myAddress) return;
+ getRewardShares(myAddress);
+ };
+
+ useEffect(() => {
+ subscribeToEvent("refresh-rewardshare-list", refreshRewardShare);
+
+ return () => {
+ unsubscribeFromEvent("refresh-rewardshare-list", refreshRewardShare);
+ };
+ }, [myAddress]);
+
+ const handleNames = (address) => {
+ if (!address) return undefined;
+ if (names[address]) return names[address];
+ if (names[address] === null) return address;
+ getName(address);
+ return address;
+ };
+
+ const handleAccountInfos = (address, field) => {
+ if (!address) return undefined;
+ if (accountInfos[address]) return accountInfos[address]?.[field];
+ if (accountInfos[address] === null) return undefined;
+ getAccountInfo(address, true);
+ return undefined;
+ };
+
+ const calculateBlocksRemainingToLevel1 = (address) => {
+ if (!address) return undefined;
+ if (!accountInfos[address]) return undefined;
+ return 7200 - accountInfos[address]?.blocksMinted || 0;
+ };
+
+ const getNodeInfos = async () => {
+ try {
+ const url = `${getBaseApiReact()}/admin/status`;
+ const response = await fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ const data = await response.json();
+ setNodeInfos(data);
+ } catch (error) {
+ console.error("Request failed", error);
+ }
+ };
+
+ const getRewardShares = useCallback(async (address) => {
+ try {
+ const url = `${getBaseApiReact()}/addresses/rewardshares?involving=${address}`;
+ const response = await fetch(url);
+ if (!response.ok) {
+ throw new Error("network error");
+ }
+ const data = await response.json();
+ setRewardShares(data);
+ } catch (error) {}
+ }, []);
+
+ const addMintingAccount = useCallback(async (val) => {
+ try {
+ setIsLoading(true);
+ return await new Promise((res, rej) => {
+ chrome?.runtime?.sendMessage(
+ {
+ action: "ADMIN_ACTION",
+ type: "qortalRequest",
+ payload: {
+ type: "addmintingaccount",
+ value: val,
+ },
+ },
+ (response) => {
+ if (response.error) {
+ rej({ message: response.error });
+ return;
+ } else {
+ res(response);
+ setMintingKey("");
+ setTimeout(() => {
+ getMintingAccounts();
+ }, 300);
+ }
+ }
+ );
+ });
+ } catch (error) {
+ setInfo({
+ type: "error",
+ message: error?.message || "Unable to add minting account",
+ });
+ setOpenSnack(true);
+ } finally {
+ setIsLoading(false);
+ }
+ }, []);
+
+ const removeMintingAccount = useCallback(async (val, acct) => {
+ try {
+ setIsLoading(true);
+ return await new Promise((res, rej) => {
+
+ chrome?.runtime?.sendMessage(
+ {
+ action: "ADMIN_ACTION",
+ type: "qortalRequest",
+ payload: {
+ type: "removemintingaccount",
+ value: val,
+ },
+ },
+ (response) => {
+ if (response.error) {
+ rej({ message: response.error });
+ return;
+ } else {
+ res(response);
+
+ setTimeout(() => {
+ getMintingAccounts();
+ }, 300);
+ }
+ }
+ );
+ });
+ } catch (error) {
+ setInfo({
+ type: "error",
+ message: error?.message || "Unable to remove minting account",
+ });
+ setOpenSnack(true);
+ } finally {
+ setIsLoading(false);
+ }
+ }, []);
+
+ const createRewardShare = useCallback(async (publicKey, recipient) => {
+ const fee = await getFee("REWARD_SHARE");
+ await show({
+ message: "Would you like to perform an REWARD_SHARE transaction?",
+ publishFee: fee.fee + " QORT",
+ });
+ return await new Promise((res, rej) => {
+
+
+ chrome?.runtime?.sendMessage(
+ {
+ action: "createRewardShare",
+ payload: {
+ recipientPublicKey: publicKey,
+ },
+ },
+ (response) => {
+ if (response?.error) {
+ rej({ message: response.error });
+ return
+ } else {
+ setTxList((prev) => [
+ {
+ recipient,
+ ...response,
+ type: "add-rewardShare",
+ label: `Add rewardshare: awaiting confirmation`,
+ labelDone: `Add rewardshare: success!`,
+ done: false,
+ },
+ ...prev,
+ ]);
+ res(response);
+ return;
+ };
+ }
+ );
+ });
+ }, []);
+
+ const getRewardSharePrivateKey = useCallback(async (publicKey) => {
+ return await new Promise((res, rej) => {
+ chrome?.runtime?.sendMessage(
+ {
+ action: "getRewardSharePrivateKey",
+ payload: {
+ recipientPublicKey: publicKey,
+ },
+ },
+ (response) => {
+ if (response?.error) {
+ rej({ message: response.error });
+ return
+ } else {
+ res(response);
+ return;
+ };
+ }
+ );
+ });
+ }, []);
+
+ const startMinting = async () => {
+ try {
+ setIsLoading(true);
+ const findRewardShare = rewardShares?.find(
+ (item) =>
+ item?.recipient === myAddress && item?.mintingAccount === myAddress
+ );
+ if (findRewardShare) {
+ const privateRewardShare = await getRewardSharePrivateKey(
+ accountInfo?.publicKey
+ );
+ addMintingAccount(privateRewardShare);
+ } else {
+ await createRewardShare(accountInfo?.publicKey, myAddress);
+ const privateRewardShare = await getRewardSharePrivateKey(
+ accountInfo?.publicKey
+ );
+ addMintingAccount(privateRewardShare);
+ }
+ } catch (error) {
+ setInfo({
+ type: "error",
+ message: error?.message || "Unable to start minting",
+ });
+ setOpenSnack(true);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const getPublicKeyFromAddress = async (address) => {
+ const url = `${getBaseApiReact()}/addresses/publickey/${address}`;
+ const response = await fetch(url);
+ const data = await response.text();
+ return data;
+ };
+
+ const checkIfMinterGroup = async (address) => {
+ const url = `${getBaseApiReact()}/groups/member/${address}`;
+ const response = await fetch(url);
+ const data = await response.json();
+ return !!data?.find((grp) => grp?.groupId?.toString() === "694");
+ };
+
+ const removeRewardShare = useCallback(async (rewardShare) => {
+ return await new Promise((res, rej) => {
+
+ chrome?.runtime?.sendMessage(
+ {
+ action: "removeRewardShare",
+ payload: {
+ rewardShareKeyPairPublicKey: rewardShare.rewardSharePublicKey,
+ recipient: rewardShare.recipient,
+ percentageShare: -1,
+ },
+ },
+ (response) => {
+ if (response?.error) {
+ rej({ message: response.error });
+ return
+ } else {
+ res(response);
+ setTxList((prev) => [
+ {
+ ...rewardShare,
+ ...response,
+ type: "remove-rewardShare",
+ label: `Remove rewardshare: awaiting confirmation`,
+ labelDone: `Remove rewardshare: success!`,
+ done: false,
+ },
+ ...prev,
+ ]);
+ return;
+ };
+ }
+ );
+ });
+ }, []);
+
+ const handleRemoveRewardShare = async (rewardShare) => {
+ try {
+ setIsLoading(true);
+
+ const privateRewardShare = await removeRewardShare(rewardShare);
+ } catch (error) {
+ setInfo({
+ type: "error",
+ message: error?.message || "Unable to remove reward share",
+ });
+ setOpenSnack(true);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const createRewardShareForPotentialMinter = async (receiver) => {
+ try {
+ setIsLoading(true);
+ const confirmReceiver = await getNameOrAddress(receiver);
+ if (confirmReceiver.error)
+ throw new Error("Invalid receiver address or name");
+ const isInMinterGroup = await checkIfMinterGroup(confirmReceiver)
+ if(!isInMinterGroup) throw new Error('Account not in Minter Group')
+ const publicKey = await getPublicKeyFromAddress(confirmReceiver);
+ const findRewardShare = rewardShares?.find(
+ (item) =>
+ item?.recipient === confirmReceiver &&
+ item?.mintingAccount === myAddress
+ );
+ if (findRewardShare) {
+ const privateRewardShare = await getRewardSharePrivateKey(publicKey);
+ setRewardsharekey(privateRewardShare);
+ } else {
+ await createRewardShare(publicKey, confirmReceiver);
+ const privateRewardShare = await getRewardSharePrivateKey(publicKey);
+ setRewardsharekey(privateRewardShare);
+ }
+ } catch (error) {
+ setInfo({
+ type: "error",
+ message: error?.message || "Unable to create reward share",
+ });
+ setOpenSnack(true);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ getNodeInfos();
+ getMintingAccounts();
+ }, []);
+
+ useEffect(() => {
+ if (!myAddress) return;
+ getRewardShares(myAddress);
+
+ getAccountInfo(myAddress);
+ }, [myAddress]);
+
+ const _blocksNeed = () => {
+ if (accountInfo?.level === 0) {
+ return 7200;
+ } else if (accountInfo?.level === 1) {
+ return 72000;
+ } else if (accountInfo?.level === 2) {
+ return 201600;
+ } else if (accountInfo?.level === 3) {
+ return 374400;
+ } else if (accountInfo?.level === 4) {
+ return 618400;
+ } else if (accountInfo?.level === 5) {
+ return 964000;
+ } else if (accountInfo?.level === 6) {
+ return 1482400;
+ } else if (accountInfo?.level === 7) {
+ return 2173600;
+ } else if (accountInfo?.level === 8) {
+ return 3037600;
+ } else if (accountInfo?.level === 9) {
+ return 4074400;
+ }
+ };
+
+ const handleClose = () => {
+ setOpenSnack(false);
+ setTimeout(() => {
+ setInfo(null);
+ }, 250);
+ };
+
+ const _levelUpBlocks = () => {
+ if (
+ accountInfo?.blocksMinted === undefined ||
+ nodeInfos?.height === undefined
+ )
+ return null;
+ let countBlocks =
+ _blocksNeed() -
+ (accountInfo?.blocksMinted + accountInfo?.blocksMintedAdjustment);
+
+ let countBlocksString = countBlocks.toString();
+ return "" + countBlocksString;
+ };
+
+ const showAndCopySponsorshipKey = async (rs) => {
+ try {
+ const sponsorshipKey = await getRewardSharePrivateKey(
+ rs?.rewardSharePublicKey
+ );
+ await showKey({
+ message: sponsorshipKey,
+ });
+ } catch (error) {}
+ };
+
+
+ return (
+
+ );
+};
diff --git a/src/components/TaskManager/TaskManger.tsx b/src/components/TaskManager/TaskManger.tsx
index 35e0a51..24eead1 100644
--- a/src/components/TaskManager/TaskManger.tsx
+++ b/src/components/TaskManager/TaskManger.tsx
@@ -12,6 +12,7 @@ import TaskAltIcon from "@mui/icons-material/TaskAlt";
import ExpandLess from "@mui/icons-material/ExpandLess";
import ExpandMore from "@mui/icons-material/ExpandMore";
import { MyContext, getBaseApiReact, isMobile } from "../../App";
+import { executeEvent } from "../../utils/events";
export const TaskManger = ({ getUserInfo }) => {
const { txList, setTxList, memberGroups } = useContext(MyContext);
@@ -39,7 +40,7 @@ export const TaskManger = ({ getUserInfo }) => {
await new Promise((res) =>
setTimeout(() => {
res(null);
- }, 300000)
+ }, 60000)
);
setTxList((prev) => {
let previousData = [...prev];
@@ -62,7 +63,7 @@ export const TaskManger = ({ getUserInfo }) => {
}
};
- intervals.current[signature] = setInterval(getAnswer, 120000);
+ intervals.current[signature] = setInterval(getAnswer, 60000);
};
useEffect(() => {
@@ -96,7 +97,15 @@ export const TaskManger = ({ getUserInfo }) => {
}
});
- prev.forEach((tx) => {
+
+
+ return previousData;
+ });
+ }, [memberGroups, getUserInfo]);
+
+ useEffect(()=> {
+
+ txList.forEach((tx) => {
if (
["created-common-secret", "joined-group-request", "join-request-accept"].includes(
tx?.type
@@ -113,11 +122,17 @@ export const TaskManger = ({ getUserInfo }) => {
getStatus({ signature: tx.signature }, getUserInfo);
}
}
+ if((tx?.type === "remove-rewardShare" || tx?.type === "add-rewardShare") && tx?.signature && !tx.done){
+ if (!intervals.current[tx.signature]) {
+ const sendEventForRewardShare = ()=> {
+ executeEvent('refresh-rewardshare-list', {})
+ }
+ getStatus({ signature: tx.signature }, sendEventForRewardShare);
+ }
+ }
});
- return previousData;
- });
- }, [memberGroups, getUserInfo]);
+ }, [txList])
if (isMobile || txList?.length === 0 || txList.every((item) => item?.done))
return null;
@@ -128,9 +143,9 @@ export const TaskManger = ({ getUserInfo }) => {
= DYNAMIC_FEE_TIMESTAMP) {
+ this.fee = _address === recipient ? 0 : 0.01
+ } else {
+ this.fee = _address === recipient ? 0 : 0.001
+ }
+ }
+
+ set percentageShare(share) {
+ this._percentageShare = share * 100
+ this._percentageShareBytes = this.constructor.utils.int64ToBytes(this._percentageShare)
+ }
+
+ get params() {
+ const params = super.params
+ params.push(
+ this._recipient,
+ this._rewardShareKeyPairPublicKey,
+ this._percentageShareBytes,
+ this._feeBytes
+ )
+ return params
+ }
+}
diff --git a/src/transactions/RewardShareTransaction.ts b/src/transactions/RewardShareTransaction.ts
new file mode 100644
index 0000000..8419432
--- /dev/null
+++ b/src/transactions/RewardShareTransaction.ts
@@ -0,0 +1,60 @@
+// @ts-nocheck
+
+import TransactionBase from './TransactionBase'
+
+import { Sha256 } from 'asmcrypto.js'
+import nacl from '../deps/nacl-fast'
+import ed2curve from '../deps/ed2curve'
+import { DYNAMIC_FEE_TIMESTAMP } from '../constants/constants'
+import publicKeyToAddress from '../utils/generateWallet/publicKeyToAddress'
+
+export default class RewardShareTransaction extends TransactionBase {
+ constructor() {
+ super()
+ this.type = 38
+ }
+
+
+
+ set recipientPublicKey(recipientPublicKey) {
+ this._base58RecipientPublicKey = recipientPublicKey instanceof Uint8Array ? this.constructor.Base58.encode(recipientPublicKey) : recipientPublicKey
+ this._recipientPublicKey = this.constructor.Base58.decode(this._base58RecipientPublicKey)
+ this.recipient = publicKeyToAddress(this._recipientPublicKey)
+
+ const convertedPrivateKey = ed2curve.convertSecretKey(this._keyPair.privateKey)
+ const convertedPublicKey = ed2curve.convertPublicKey(this._recipientPublicKey)
+ const sharedSecret = new Uint8Array(32)
+
+ nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedPublicKey)
+
+ this._rewardShareSeed = new Sha256().process(sharedSecret).finish().result
+ this._base58RewardShareSeed = this.constructor.Base58.encode(this._rewardShareSeed)
+ this._rewardShareKeyPair = nacl.sign.keyPair.fromSeed(this._rewardShareSeed)
+
+ if (new Date(this._timestamp).getTime() >= DYNAMIC_FEE_TIMESTAMP) {
+ this.fee = (recipientPublicKey === this.constructor.Base58.encode(this._keyPair.publicKey) ? 0 : 0.01)
+ } else {
+ this.fee = (recipientPublicKey === this.constructor.Base58.encode(this._keyPair.publicKey) ? 0 : 0.001)
+ }
+ }
+
+ set recipient(recipient) {
+ this._recipient = recipient instanceof Uint8Array ? recipient : this.constructor.Base58.decode(recipient)
+ }
+
+ set percentageShare(share) {
+ this._percentageShare = share * 100
+ this._percentageShareBytes = this.constructor.utils.int64ToBytes(this._percentageShare)
+ }
+
+ get params() {
+ const params = super.params
+ params.push(
+ this._recipient,
+ this._rewardShareKeyPair.publicKey,
+ this._percentageShareBytes,
+ this._feeBytes
+ )
+ return params
+ }
+}
diff --git a/src/transactions/transactions.ts b/src/transactions/transactions.ts
index 400418c..cbf582f 100644
--- a/src/transactions/transactions.ts
+++ b/src/transactions/transactions.ts
@@ -17,7 +17,8 @@ import RegisterNameTransaction from './RegisterNameTransaction.js'
import VoteOnPollTransaction from './VoteOnPollTransaction.js'
import CreatePollTransaction from './CreatePollTransaction.js'
import DeployAtTransaction from './DeployAtTransaction.js'
-
+import RewardShareTransaction from './RewardShareTransaction.js'
+import RemoveRewardShareTransaction from './RemoveRewardShareTransaction.js'
export const transactionTypes = {
3: RegisterNameTransaction,
@@ -36,7 +37,9 @@ export const transactionTypes = {
29: GroupInviteTransaction,
30: CancelGroupInviteTransaction,
31: JoinGroupTransaction,
- 32: LeaveGroupTransaction
+ 32: LeaveGroupTransaction,
+ 38: RewardShareTransaction,
+ 381: RemoveRewardShareTransaction
}