feat: add ionic swap balance validation

This commit is contained in:
AzizbekFayziyev 2026-03-16 17:00:55 +05:00
parent a3f07a334d
commit 2632f3f6bb
4 changed files with 90 additions and 67 deletions

35
package-lock.json generated
View file

@ -192,7 +192,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz",
"integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==",
"dev": true,
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.21.4",
@ -1060,7 +1059,6 @@
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.21.4.tgz",
"integrity": "sha512-l9xd3N+XG4fZRxEP3vXdK6RW7vN1Uf5dxzRC/09wV86wqZ/YYQooBIGNsiRdfNR3/q2/5pPzV4B54J/9ctX5jw==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.20.2"
},
@ -1662,7 +1660,6 @@
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.21.0.tgz",
"integrity": "sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.18.6",
"@babel/helper-module-imports": "^7.18.6",
@ -3545,7 +3542,6 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@ -4191,7 +4187,6 @@
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz",
"integrity": "sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==",
"dev": true,
"peer": true,
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
@ -4569,7 +4564,6 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.57.1.tgz",
"integrity": "sha512-1MeobQkQ9tztuleT3v72XmY0XuKXVXusAhryoLuU5YZ+mXoYKZP9SQ7Flulh1NX4DTjpGTc2b/eMu4u7M7dhnQ==",
"dev": true,
"peer": true,
"dependencies": {
"@eslint-community/regexpp": "^4.4.0",
"@typescript-eslint/scope-manager": "5.57.1",
@ -4623,7 +4617,6 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.57.1.tgz",
"integrity": "sha512-hlA0BLeVSA/wBPKdPGxoVr9Pp6GutGoY380FEhbVi0Ph4WNe8kLvqIRx76RSQt1lynZKfrXKs0/XeEk4zZycuA==",
"dev": true,
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.57.1",
"@typescript-eslint/types": "5.57.1",
@ -5042,7 +5035,6 @@
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@ -5129,7 +5121,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@ -5683,7 +5674,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@ -6229,7 +6219,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001669",
"electron-to-chromium": "^1.5.41",
@ -7028,7 +7017,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@ -7321,7 +7309,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@ -8629,7 +8616,6 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.37.0.tgz",
"integrity": "sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw==",
"dev": true,
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.4.0",
@ -8718,7 +8704,6 @@
"integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
@ -8831,7 +8816,6 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@ -9136,7 +9120,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@ -11616,7 +11599,6 @@
"resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz",
"integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==",
"dev": true,
"peer": true,
"dependencies": {
"@jest/core": "^27.5.1",
"import-local": "^3.0.2",
@ -14509,7 +14491,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@ -15490,7 +15471,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.1.1",
@ -16647,7 +16627,6 @@
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz",
"integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==",
"dev": true,
"peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@ -16766,7 +16745,6 @@
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@ -17052,7 +17030,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@ -17133,7 +17110,6 @@
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.0"
@ -17166,7 +17142,6 @@
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==",
"dev": true,
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@ -17761,7 +17736,6 @@
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"rollup": "dist/bin/rollup"
},
@ -17926,7 +17900,6 @@
"resolved": "https://registry.npmjs.org/sass/-/sass-1.62.0.tgz",
"integrity": "sha512-Q4USplo4pLYgCi+XlipZCWUQz5pkg/ruSSgJ0WRDSb/+3z9tXUOkQ7QPYn4XrhZKYAK4HlpaQecRwKLJX6+DBg==",
"dev": true,
"peer": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
@ -19574,7 +19547,6 @@
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
"dev": true,
"peer": true,
"engines": {
"node": ">=10"
},
@ -19689,7 +19661,6 @@
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -20031,7 +20002,6 @@
"integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.6",
@ -20079,7 +20049,6 @@
"integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@discoveryjs/json-ext": "^0.5.0",
"@webpack-cli/configtest": "^2.1.1",
@ -20160,7 +20129,6 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@ -20218,7 +20186,6 @@
"integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/bonjour": "^3.5.9",
"@types/connect-history-api-fallback": "^1.3.5",
@ -20278,7 +20245,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@ -20710,7 +20676,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",

View file

@ -413,6 +413,9 @@ function App() {
id: e.id,
method: 'FINALIZE_IONIC_SWAP_REQUEST',
name: 'Ionic Swap',
sendingAmount: sendingAmount.toFixed(),
sendingAsset,
sendingAssetId: swap?.currentAssetID,
params: [
{
key: 'Address',

View file

@ -33,10 +33,21 @@ const OuterConfirmation = () => {
const [showFullComment, setShowFullComment] = useState(false);
const req = reqs[reqIndex] || {};
const { id, name, params, method, destinations, assetId } = req;
const {
id,
name,
params,
method,
destinations,
assetId,
sendingAmount,
sendingAsset,
sendingAssetId,
} = req;
const isTransferMethod = name?.toLowerCase() === 'transfer';
const isBurnMethod = name?.toLowerCase() === 'burn_asset';
const isIonicSwapMethod = method === 'FINALIZE_IONIC_SWAP_REQUEST';
const isMultipleDestinations = destinations && destinations.length > 0;
@ -105,24 +116,51 @@ const OuterConfirmation = () => {
? new Decimal(state.wallet?.balance || 0)
: new Decimal(state.wallet?.assets?.find((a) => a.assetId === assetId)?.balance || 0);
const feeBig = new Decimal(fee);
const swapAmount = new Decimal(sendingAmount || 0);
const sendingBalance =
sendingAssetId === ZANO_ASSET_ID
? zanoBalance
: new Decimal(
state.wallet?.assets?.find((a) => a.assetId === sendingAssetId)?.balance || 0,
);
const notEnoughFee = useMemo(() => {
return zanoBalance.lessThan(feeBig);
}, [zanoBalance, feeBig]);
const notEnoughAmount = useMemo(() => {
if (!isTransferMethod) return false;
const { notEnoughAmount, notEnoughFee } = useMemo(() => {
if (assetId === ZANO_ASSET_ID) {
const enoughForFee = zanoBalance.greaterThanOrEqualTo(feeBig);
const enoughForTotal = zanoBalance.greaterThanOrEqualTo(rawTotalAmount.plus(feeBig));
return {
notEnoughAmount: !enoughForTotal,
notEnoughFee: !enoughForFee,
};
return zanoBalance.lessThan(rawTotalAmount.plus(feeBig));
}
return {
notEnoughAmount: assetBalance.lessThan(rawTotalAmount),
notEnoughFee: zanoBalance.lessThan(feeBig),
};
}, [assetId, zanoBalance, assetBalance, rawTotalAmount, feeBig]);
return assetBalance.lessThan(rawTotalAmount);
}, [isTransferMethod, assetId, zanoBalance, assetBalance, rawTotalAmount, feeBig]);
const notEnoughSwapAmount = useMemo(() => {
if (!isIonicSwapMethod) return false;
if (sendingAsset?.asset_id === ZANO_ASSET_ID) {
return zanoBalance.lessThan(swapAmount.plus(feeBig));
}
return sendingBalance.lessThan(swapAmount);
}, [
isIonicSwapMethod,
sendingAsset?.asset_id,
sendingBalance,
swapAmount,
zanoBalance,
feeBig,
]);
const disabled = accepting || denying;
const insufficientBalance =
notEnoughFee ||
(isTransferMethod && notEnoughAmount) ||
(isIonicSwapMethod && notEnoughSwapAmount);
const getConfirmationContent = () => {
if (isTransferMethod) {
@ -358,26 +396,42 @@ const OuterConfirmation = () => {
<div className={styles.confirmation__content}>{getConfirmationContent()}</div>
<div className={styles.confirmation__bottom}>
<div className={styles.confirmation__bottom_row}>
<h5 className={styles.label}>
Transaction fee <InfoTooltip title="Total network fee" />
</h5>
<p className={`${styles.value} ${notEnoughFee ? styles.error : ''}`}>
{fee} ZANO
</p>
</div>
{(isTransferMethod || isBurnMethod || isIonicSwapMethod) && (
<>
<div className={styles.confirmation__bottom_row}>
<h5 className={styles.label}>
Transaction fee <InfoTooltip title="Total network fee" />
</h5>
<p className={`${styles.value} ${notEnoughFee ? styles.error : ''}`}>
{fee} ZANO
</p>
</div>
<div className={styles.divider} />
{(isTransferMethod || isIonicSwapMethod) && (
<>
<div className={styles.divider} />
<div className={`${styles.confirmation__bottom_row} ${styles.total}`}>
<h5 className={styles.label}>Total</h5>
<p className={`${styles.value} ${notEnoughAmount ? styles.error : ''}`}>
{totalAmount}
</p>
</div>
<div
className={`${styles.confirmation__bottom_row} ${styles.total}`}
>
<h5 className={styles.label}>Total</h5>
<p
className={`${styles.value} ${(isTransferMethod ? notEnoughAmount : notEnoughSwapAmount) ? styles.error : ''}`}
>
{isTransferMethod
? totalAmount
: Number(sendingAmount).toLocaleString()}
</p>
</div>
{(notEnoughFee || notEnoughAmount) && (
<div className={styles.confirmation__bottom_error}>Insufficient balance</div>
{insufficientBalance && (
<div className={styles.confirmation__bottom_error}>
Insufficient balance
</div>
)}
</>
)}
</>
)}
<div className={styles.confirmation__bottom_buttons}>
@ -393,7 +447,7 @@ const OuterConfirmation = () => {
<Button
className={styles.btn}
disabled={disabled || notEnoughAmount || notEnoughFee}
disabled={disabled || insufficientBalance}
onClick={acceptClick}
>
Confirm

View file

@ -44,6 +44,7 @@ export type SwapRequest = {
destinationAssetAmount: string;
currentAsset: string;
currentAssetAmount: string;
currentAssetID: string;
};
};