add: BURN_ASSET method

This commit is contained in:
AzizbekFayziyev 2025-06-21 16:33:14 +05:00
parent 0effb5e2b3
commit 7cc76f018f
7 changed files with 438 additions and 153 deletions

View file

@ -41,49 +41,11 @@ import OuterConfirmation from "./components/OuterConfirmation/OuterConfirmation"
import Formatters from "./utils/formatters";
import Big from "big.js";
import swapModalStyles from "./styles/SwapModal.module.scss";
import useGetAsset from "./hooks/useGetAsset";
// Types
type dispatchType = () => void;
type destinationsType = { address: string, amount: number }[];
type transferType = { transfer: { sender: string, destination: string, destinations: destinationsType, amount: string, asset: { ticker: string }, comment?: 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;
};
type AssetWhitelistReq = {
id: string;
asset_id: string;
asset_name: string;
}
import { AcceptSwapReq, AssetWhitelistReq, dispatchType, RequestType, SwapRequest, transferType } from "../types";
function App() {
const { state, dispatch } = useContext(Store);
const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
const { getAssetById } = useGetAsset();
const [incorrectPassword, setIncorrectPassword] = useState(false);
const [loggedIn, setLoggedIn] = useState(false);
@ -92,11 +54,6 @@ function App() {
const [connectOpened, setConnectOpened] = useState(false);
// Flags of display
// creatingPassword flag has an effect only in case of loggedIn flag is false.
// creatingPassword flag means whether to show the password create screen or existing password enter screen.
// const creatingPassword = !passwordExists();
useEffect(() => {
async function loadLogin() {
const password = await getSessionPassword();
@ -560,6 +517,29 @@ function App() {
}
}
async function getBurnAssetRequests() {
const response = await fetchBackground({ method: "GET_BURN_ASSET_REQUESTS" });
const burnRequests = response.data;
const pageReqs = burnRequests.map((e: any) => {
const data = e.burn;
return {
id: e.id,
method: "FINALIZE_BURN_ASSET_REQUEST",
name: "BURN_ASSET",
params: [
data
],
};
});
if (pageReqs.length > 0) {
goTo(OuterConfirmation, { reqs: pageReqs });
}
}
await getBurnAssetRequests();
await getAliasCreationRequests();
await getIonicSwapRequests();
await getSignRequests();

View file

@ -2,7 +2,7 @@ import React from "react";
import Button, { ButtonThemes } from "../UI/Button/Button";
import styles from "./OuterConfirmation.module.scss";
import { useState, useEffect } from "react";
import { fetchBackground } from "../../utils/utils";
import { fetchBackground, shortenAddress } from "../../utils/utils";
import customTokenIcon from "../../assets/tokens-svg/custom-token.svg";
import banditIcon from "../../assets/tokens-svg/bandit-icon.svg";
import zanoIcon from "../../assets/tokens-svg/zano.svg";
@ -11,6 +11,8 @@ import ethIcon from "../../assets/tokens-svg/eth.svg";
import arrowIcon from "../../assets/svg/arrow-blue.svg";
import InfoTooltip from "../UI/InfoTooltip";
import { getCurrent, goBack } from "react-chrome-extension-router";
import { BurnAssetDataType } from "../../../types";
import { BANDIT_ASSET_ID, ZANO_ASSET_ID } from "../../../constants";
interface ParamsType {
key: number;
@ -29,13 +31,14 @@ const OuterConfirmation = () => {
const [reqIndex, setReqIndex] = useState(0);
const [accepting, setAccepting] = useState(false);
const [denying, setDenying] = useState(false);
const [showFullAddresses, setShowFullAddresses] = useState(false);
const [showFullItems, setShowFullItems] = useState(false);
const [showFullComment, setShowFullComment] = useState(false);
const req = reqs[reqIndex] || {};
const { id, name, params, method, destinations } = req;
const isTransferMethod = name?.toLowerCase() === "transfer";
const isBurnMethod = name?.toLowerCase() === "burn_asset";
const isMultipleDestinations = destinations && destinations.length > 0;
@ -56,7 +59,6 @@ const OuterConfirmation = () => {
}
}
async function acceptClick() {
setAccepting(true);
await fetchBackground({ method, id, success: true });
@ -88,7 +90,188 @@ const OuterConfirmation = () => {
const disabled = accepting || denying;
console.log("FINALIZA TRANSACTION", req);
const getConfirmationName = () => {
if (isTransferMethod) {
return "Please confirm the transfer details";
} else if (isBurnMethod) {
return "BURN ASSET"
} else {
return name
}
}
const getConfirmationContent = () => {
if (isTransferMethod) {
return (
<>
<div className={styles.confirmation__block}>
<div className={styles.row}>
<h5>From</h5>
<p>{transactionParams?.F}</p>
</div>
<div className={styles.row}>
<h5>Asset</h5>
<p>{getAssetIcon(transactionParams?.Asset)} {transactionParams?.Asset}</p>
</div>
<div className={styles.row}>
<h5>Amount</h5>
<p>{totalAmount}</p>
</div>
<div className={styles.col}>
<h5>Comment</h5>
<p>{(transactionParams?.Comment?.length > 60 && !showFullComment) ?
<>
{transactionParams?.Comment?.slice(0, 60)}...
<button className={styles.commentBtn} onClick={() => setShowFullComment(true)}>Show more</button>
</>
:
<>
{transactionParams?.Comment}
{showFullComment && <button className={`${styles.commentBtn} ${styles.less}`} onClick={() => setShowFullComment(false)}>Show less</button>}
</>
}</p>
</div>
</div>
<div className={styles.confirmation__block}>
<div className={styles.row}>
<h5>To</h5>
<p>{isMultipleDestinations ? <>{destinations?.length} addresses</> : transactionParams?.To}</p>
</div>
{!isMultipleDestinations && <div className={styles.row}>
<h5>Amount</h5>
<p>{totalAmount}</p>
</div>}
</div>
{isMultipleDestinations && (
<>
<button
onClick={() => setShowFullItems(prev => !prev)}
className={styles.confirmation__showAddressesBtn}
>
Show addresses <img style={{ transform: `rotate(${showFullItems ? '180deg' : 0})` }} width={18} src={arrowIcon} alt="arrow" />
</button>
{showFullItems && destinations?.map((item: DestionationType, idx: number) => (
<div className={styles.confirmation__destinationWrapper} key={idx}>
<p className={styles.title}>RECIPIENT {idx + 1}</p>
<div className={styles.confirmation__block}>
<div className={styles.row}>
<h5>To</h5>
<p>{item.address}</p>
</div>
<div className={styles.row}>
<h5>Amount</h5>
<p>{item.amount}</p>
</div>
</div>
</div>
))}
</>
)}
</>
)
} else if (isBurnMethod) {
const {
assetId,
burnAmount,
nativeAmount,
pointTxToAddress,
serviceEntries
}: BurnAssetDataType = params[0];
const getIconByAsseetId = () => {
if (assetId === ZANO_ASSET_ID) {
return "ZANO"
} else if (assetId === BANDIT_ASSET_ID) {
return "BANDIT"
} else {
return assetId
}
}
return (
<>
<div className={styles.confirmation__block}>
<div className={styles.row}>
<h5>Asset</h5>
<p>{getAssetIcon(getIconByAsseetId())} {shortenAddress(assetId, 6, 6)}</p>
</div>
<div className={styles.row}>
<h5>Burn Amount</h5>
<p>{burnAmount}</p>
</div>
{nativeAmount && <div className={styles.row}>
<h5>Native Amount</h5>
<p>{nativeAmount}</p>
</div>}
{pointTxToAddress && <div className={styles.row}>
<h5>Send Tx To</h5>
<p>{shortenAddress(pointTxToAddress, 6, 6)}</p>
</div>}
</div>
{serviceEntries && <button
onClick={() => setShowFullItems(prev => !prev)}
className={styles.confirmation__showAddressesBtn}
>
Show service entries <img style={{ transform: `rotate(${showFullItems ? '180deg' : 0})` }} width={18} src={arrowIcon} alt="arrow" />
</button>}
{showFullItems && serviceEntries?.map((item, idx) => {
const dataLength = serviceEntries?.length || 1;
return <div className={styles.confirmation__destinationWrapper} key={idx}>
{dataLength > 1 && <p className={styles.title}>
Service Entries {idx + 1}
</p>}
<div className={styles.confirmation__block}>
<div className={styles.row}>
<h5>Body</h5>
<p>{shortenAddress(item.body, 6, 6)}</p>
</div>
<div className={styles.row}>
<h5>Flags</h5>
<p>{item.flags}</p>
</div>
<div className={styles.row}>
<h5>Instruction</h5>
<p>{item.instruction}</p>
</div>
{item.security && <div className={styles.row}>
<h5>Security</h5>
<p>{shortenAddress(item.security, 6, 6)}</p>
</div>}
<div className={styles.row}>
<h5>Service Id</h5>
<p>{item.service_id}</p>
</div>
</div>
</div>
})}
</>
);
} else {
return (
<div>
<div className={styles.confirmation__block}>
{Array.isArray(params) && params?.map((item: ParamsType, idx: number) => (
<div key={idx} className={styles.row}>
<h5>{item.key}</h5>
<p>{item.value}</p>
</div>
))}
</div>
</div>
)
}
}
if (!req) {
return <div>No request found.</div>;
@ -98,99 +281,15 @@ const OuterConfirmation = () => {
<div className={styles.confirmation}>
<h3 className={styles.confirmation__title}>Request Confirmation</h3>
<h5 className={styles.confirmation__subtitle}>
{isTransferMethod ? "Please confirm the transfer details" : name}
{getConfirmationName()}
</h5>
<div className={styles.confirmation__content}>
{isTransferMethod ? (
<>
<div className={styles.confirmation__block}>
<div className={styles.row}>
<h5>From</h5>
<p>{transactionParams?.From}</p>
</div>
<div className={styles.row}>
<h5>Asset</h5>
<p>{getAssetIcon(transactionParams?.Asset)} {transactionParams?.Asset}</p>
</div>
<div className={styles.row}>
<h5>Amount</h5>
<p>{totalAmount}</p>
</div>
<div className={styles.col}>
<h5>Comment</h5>
<p>{(transactionParams?.Comment?.length > 60 && !showFullComment) ?
<>
{transactionParams?.Comment?.slice(0, 60)}...
<button className={styles.commentBtn} onClick={() => setShowFullComment(true)}>Show more</button>
</>
:
<>
{transactionParams?.Comment}
{showFullComment && <button className={`${styles.commentBtn} ${styles.less}`} onClick={() => setShowFullComment(false)}>Show less</button>}
</>
}</p>
</div>
</div>
<div className={styles.confirmation__block}>
<div className={styles.row}>
<h5>To</h5>
<p>{isMultipleDestinations ? <>{destinations?.length} addresses</> : transactionParams?.To}</p>
</div>
{!isMultipleDestinations && <div className={styles.row}>
<h5>Amount</h5>
<p>{totalAmount}</p>
</div>}
</div>
{isMultipleDestinations && (
<>
<button
onClick={() => setShowFullAddresses(prev => !prev)}
className={styles.confirmation__showAddressesBtn}
>
Show addresses <img style={{ transform: `rotate(${showFullAddresses ? '180deg' : 0})` }} width={18} src={arrowIcon} alt="arrow" />
</button>
{showFullAddresses && destinations?.map((item: DestionationType, idx: number) => (
<div className={styles.confirmation__destinationWrapper} key={idx}>
<p className={styles.title}>RECIPIENT {idx + 1}</p>
<div className={styles.confirmation__block}>
<div className={styles.row}>
<h5>To</h5>
<p>{item.address}</p>
</div>
<div className={styles.row}>
<h5>Amount</h5>
<p>{item.amount}</p>
</div>
</div>
</div>
))}
</>
)}
</>
) : (
<div>
<div className={styles.confirmation__block}>
{Array.isArray(params) && params?.map((item: ParamsType, idx: number) => (
<div key={idx} className={styles.row}>
<h5>{item.key}</h5>
<p>{item.value}</p>
</div>
))}
</div>
</div>
)}
{getConfirmationContent()}
</div>
<div className={styles.confirmation__bottom}>
{isTransferMethod && <>
{isTransferMethod || isBurnMethod && <>
<div className={styles.confirmation__bottom_fee}>
<h5>
Transaction fee <InfoTooltip title="Total network fee" />
@ -198,12 +297,14 @@ const OuterConfirmation = () => {
<p>0.01 ZANO</p>
</div>
<div className={styles.divider} />
{isTransferMethod && <>
<div className={styles.divider} />
<div className={styles.confirmation__bottom_total}>
<h5>Total</h5>
<p>{totalAmount}</p>
</div>
<div className={styles.confirmation__bottom_total}>
<h5>Total</h5>
<p>{totalAmount}</p>
</div>
</>}
</>}
<div className={styles.confirmation__bottom_buttons}>

View file

@ -12,12 +12,12 @@ interface ValidationResult {
error?: string;
}
export async function fetchBackground(data: {
method: string;
password?: string;
id?: number;
success?: boolean;
credentials?: { port: 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) => {
@ -107,7 +107,7 @@ export function validateTokensInput(input: string | number, decimal_point: numbe
const roundedInput = new Decimal(dotInput).toFixed(1);
if (roundedInput.replace(/./g, '').length <= 20) {
return roundedInput;
return roundedInput;
}
}
@ -144,3 +144,15 @@ export function validateTokensInput(input: string | number, decimal_point: numbe
valid: true
};
}
export const shortenAddress = (
address: string | undefined,
startAmount: number = 5,
endAmount: number = 3
) => {
if (!address) {
return "";
}
return address.slice(0, startAmount) + "..." + address.slice(-endAmount);
};

View file

@ -15,7 +15,8 @@ import {
getWhiteList,
getAssetInfo,
createAlias,
addAssetToWhitelist
addAssetToWhitelist,
burnAsset
} from "./wallet";
import JSONbig from "json-bigint";
@ -232,14 +233,15 @@ const signReqFinalizers: SignReqFinalizer = {};
const signReqs: unknown[] = [];
const savedRequests: Record<
"IONIC_SWAP" | "ACCEPT_IONIC_SWAP" | "CREATE_ALIAS" | "TRANSFER" | "ASSETS_WHITELIST_ADD",
"IONIC_SWAP" | "ACCEPT_IONIC_SWAP" | "CREATE_ALIAS" | "TRANSFER" | "ASSETS_WHITELIST_ADD" | "BURN_ASSET",
Record<string, PopupRequest>
> = {
IONIC_SWAP: {},
ACCEPT_IONIC_SWAP: {},
CREATE_ALIAS: {},
TRANSFER: {},
ASSETS_WHITELIST_ADD: {}
ASSETS_WHITELIST_ADD: {},
BURN_ASSET: {},
};
const allPopupIds: number[] = [];
@ -280,7 +282,9 @@ const SELF_ONLY_REQUESTS = [
"GET_WALLETS",
"FINALIZE_TRANSFER_REQUEST",
"GET_ASSETS_WHITELIST_ADD_REQUESTS",
"FINALIZE_ASSETS_WHITELIST_ADD_REQUESTS"
"FINALIZE_ASSETS_WHITELIST_ADD_REQUESTS",
"GET_BURN_ASSET_REQUESTS",
"FINALIZE_BURN_ASSET_REQUEST",
];
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
@ -321,6 +325,10 @@ interface RequestType {
asset_id?: string;
asset_name?: string;
comment: string;
burnAmount: number;
nativeAmount?: number;
pointTxToAddress?: string;
serviceEntries?: any[];
}
interface Sender {
@ -877,6 +885,45 @@ async function processRequest(request: RequestType, sender: Sender, sendResponse
)
break
}
case "BURN_ASSET":
PopupRequestsMethods.onRequestCreate(
"BURN_ASSET",
request,
sendResponse,
{
method: "FINALIZE_BURN_ASSET_REQUEST",
name: "Burn asset",
burn: request,
} as any
);
break;
case "GET_BURN_ASSET_REQUESTS":
PopupRequestsMethods.getRequestsList("BURN_ASSET", sendResponse);
break;
case "FINALIZE_BURN_ASSET_REQUEST":
PopupRequestsMethods.onRequestFinalize(
"BURN_ASSET",
request,
sendResponse,
async (req) => {
const burnReq = req.burn as any;
return burnAsset({
assetId: burnReq.assetId,
burnAmount: burnReq.burnAmount,
nativeAmount: burnReq.nativeAmount,
pointTxToAddress: burnReq.pointTxToAddress,
serviceEntries: burnReq.serviceEntries,
});
},
{
console: "Burn asset error:",
response: "Failed to burn asset",
reqNotFound: "Burn asset request not found",
}
);
break;
default:

View file

@ -511,4 +511,52 @@ export async function addAssetToWhitelist(assetId: string) {
const data = await response.json();
return data;
}
}
export const burnAsset = async ({
assetId,
burnAmount,
decimalPoint = 12,
nativeAmount = 0,
pointTxToAddress,
serviceEntries = [],
}: {
assetId: string;
burnAmount: number;
decimalPoint?: number;
nativeAmount?: number;
pointTxToAddress?: string;
serviceEntries?: {
body: string;
flags: number;
instruction: string;
security?: string;
service_id: string;
}[];
}) => {
const params: any = {
asset_id: assetId,
burn_amount: addZeros(burnAmount, decimalPoint).toFixed(0),
};
if (nativeAmount) {
params.native_amount = nativeAmount;
}
if (pointTxToAddress) {
params.point_tx_to_address = pointTxToAddress;
}
if (serviceEntries.length > 0) {
params.service_entries = serviceEntries;
}
const response = await fetchData("burn_asset", params);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
};

View file

@ -1 +1,2 @@
export const ZANO_ASSET_ID = "d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a";
export const ZANO_ASSET_ID = "d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a";
export const BANDIT_ASSET_ID = "55a8e0a730b133fb83915ba0e4335a680ae9d07a99642b17774460560f3b003d";

96
src/types/index.ts Normal file
View file

@ -0,0 +1,96 @@
// Types
export type dispatchType = () => void;
export type destinationsType = { address: string, amount: number }[];
export type transferType = {
transfer:
{
sender: string,
destination: string,
destinations: destinationsType,
amount: string,
asset: {
ticker: string
},
comment?: string,
}, id: number
};
export type RequestType = {
method: string;
assetId: string,
amount: string,
destinationAddress: string,
destinationChainId: string,
burnAmount: string;
nativeAmount?: number;
pointTxToAddress?: string;
serviceEntries?: any[];
};
export type SwapRequest = {
id: string;
swap: {
destinationAddress: string;
destinationAsset: string;
destinationAssetAmount: string;
currentAsset: string;
currentAssetAmount: string;
};
};
export type SwapProposal = {
to_finalizer: { amount: Big }[];
to_initiator: { amount: Big }[];
};
export type Asset = {
decimal_point: number;
[key: string]: any;
};
export type AcceptSwapReq = {
id: string;
hex_raw_proposal: string;
swapProposal: SwapProposal;
receivingAsset: Asset;
sendingAsset: Asset;
};
export type AssetWhitelistReq = {
id: string;
asset_id: string;
asset_name: string;
}
export interface BurnAssetRequest {
params: {
assetId: string;
burnAmount: number;
nativeAmount?: number;
pointTxToAddress?: string;
serviceEntries?: {
body: string;
flags: number;
instruction: string;
security?: string;
service_id: string;
}[];
};
[key: string]: any;
}
export interface BurnAssetDataType {
assetId: string;
burnAmount: number;
decimalPoint?: number;
nativeAmount?: number;
pointTxToAddress?: string;
serviceEntries?: {
service_id: string;
instruction: string;
body: string;
flags: number;
security?: string;
}[];
}