diff --git a/eslint.config.mjs b/eslint.config.mjs index dd1ef32d2..c49048711 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -51,6 +51,15 @@ export default tseslint.config( 'linebreak-style': 'off', 'no-undef': 'off', 'prettier/prettier': 0, + // Disable new stricter react-hooks rules from Next.js 15.5.4 + 'react-hooks/set-state-in-effect': 'off', + 'react-hooks/immutability': 'off', + 'react-hooks/set-state-in-render': 'off', + 'react-hooks/static-components': 'off', + 'react-hooks/purity': 'off', + 'react-hooks/preserve-manual-memoization': 'off', + 'react-hooks/refs': 'off', + 'react-hooks/incompatible-library': 'warn', 'sort-imports': [ 'warn', { diff --git a/install_charting_library.sh b/install_charting_library.sh index 97015b621..9873d591e 100644 --- a/install_charting_library.sh +++ b/install_charting_library.sh @@ -24,4 +24,20 @@ cp -r "$LATEST_HASH/charting_library" src/utils/ cp -r "$LATEST_HASH/datafeeds" public/ cp -r "$LATEST_HASH/datafeeds" src/utils/ +# Create index.js to export widget for Next.js compatibility +cat > "src/utils/charting_library/index.js" << 'JSEOF' +// The charting library is loaded via a UMD bundle that attaches to window.TradingView +// We provide a stub that resolves to the actual widget at runtime +export const widget = (typeof window !== 'undefined' && typeof window.TradingView !== 'undefined') + ? window.TradingView.widget + : class MockWidget {} +JSEOF + +# Create index.d.ts to re-export types +cat > "src/utils/charting_library/index.d.ts" << 'DTSEOF' +// Re-export all types from the charting library +export * from './charting_library.d' +export * from './datafeed-api.d' +DTSEOF + remove_if_directory_exists "$LATEST_HASH" \ No newline at end of file diff --git a/next.config.js b/next.config.js index 5b63899a3..51ca0db74 100644 --- a/next.config.js +++ b/next.config.js @@ -2,6 +2,7 @@ const nextConfig = { reactStrictMode: true, + transpilePackages: ['bignumber.js'], images: { remotePatterns: [ { @@ -98,13 +99,35 @@ const nextConfig = { }, ] }, - webpack(config) { + webpack(config, { isServer }) { config.module.rules.push({ test: /\.svg$/i, issuer: /\.[jt]sx?$/, use: ['@svgr/webpack'], }) + // Handle charting library - it's a UMD bundle that needs special treatment + // Use path.resolve instead of require.resolve to avoid issues when file doesn't exist yet + const path = require('path') + const fs = require('fs') + const chartingLibraryPath = path.resolve(__dirname, 'src/utils/charting_library/index.js') + + // Only add alias if the file exists (it's created by install-charting-library script) + if (fs.existsSync(chartingLibraryPath)) { + config.resolve.alias = { + ...config.resolve.alias, + 'utils/charting_library': chartingLibraryPath, + } + } + + // Fix for packages with only "exports" field (like @cosmjs) + // This ensures webpack can resolve them properly + config.resolve.extensionAlias = { + '.js': ['.js', '.ts', '.tsx'], + '.mjs': ['.mjs', '.mts'], + ...config.resolve.extensionAlias, + } + return config }, } diff --git a/package.json b/package.json index 4a30247d3..d6d1a21e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mars-v2-frontend", - "version": "3.0.2", + "version": "3.0.3", "homepage": "./", "private": false, "license": "SEE LICENSE IN LICENSE FILE", @@ -20,90 +20,90 @@ ] }, "dependencies": { - "@cosmjs/cosmwasm-stargate": "^0.36.0", + "@cosmjs/cosmwasm-stargate": "^0.37.0", "@delphi-labs/shuttle-react": "^4.3.0", - "@keplr-wallet/cosmos": "^0.12.275", - "@next/eslint-plugin-next": "^15.5.4", - "@skip-go/client": "^1.5.8", + "@keplr-wallet/cosmos": "^0.12.285", + "@next/eslint-plugin-next": "^16.0.1", + "@react-native-async-storage/async-storage": "^2.2.0", + "@skip-go/client": "^1.5.9", "@solana/web3.js": "^1.98.4", - "@tanstack/react-query": "^5.90.2", + "@tanstack/react-query": "^5.90.5", "@tanstack/react-table": "^8.21.3", "@tippyjs/react": "^4.2.6", "@vercel/analytics": "^1.5.0", "@vercel/og": "^0.8.5", "@web3modal/wagmi": "^5.1.11", - "axios": "^1.12.2", + "axios": "^1.13.1", "bignumber.js": "^9.3.1", - "bitcoinjs-lib": "^6.1.7", + "bitcoinjs-lib": "^7.0.0", "classnames": "^2.5.1", "debounce-promise": "^3.1.2", - "graphql-request": "^7.2.0", + "graphql-request": "^7.3.1", "ibc-domains-sdk": "^1.1.0", "isbot": "^5.1.31", "lodash": "^4.17.21", "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", - "mobx": "^6.13.7", + "mobx": "^6.15.0", "moment": "^2.30.1", - "next": "^15.5.4", + "next": "15.5.4", "react": "19.2.0", "react-device-detect": "^2.2.3", "react-dom": "^19.2.0", "react-draggable": "^4.5.0", "react-helmet-async": "^2.0.5", - "@react-native-async-storage/async-storage": "^2.0.0", "react-qr-code": "^2.0.18", - "react-router-dom": "^7.9.2", + "react-router-dom": "^7.9.5", "react-spring": "^10.0.3", "react-toastify": "^11.0.5", "react-use-clipboard": "^1.0.9", - "recharts": "^3.2.1", + "recharts": "^3.3.0", "sharp": "^0.34.4", "starknet": "^7.6.4", "swr": "^2.3.6", - "viem": "^2.37.8", - "wagmi": "^2.17.4", + "viem": "^2.38.5", + "wagmi": "^2.19.1", "zustand": "5.0.8" }, "devDependencies": { - "@babel/eslint-parser": "^7.28.4", - "buffer": "^6.0.3", - "crypto-browserify": "^3.12.0", - "react-native-web": "^0.19.13", - "stream-browserify": "^3.0.0", - "@eslint/compat": "^1.4.0", + "@babel/eslint-parser": "^7.28.5", + "@eslint/compat": "^1.4.1", "@svgr/webpack": "^8.1.0", "@tailwindcss/container-queries": "^0.1.1", - "@tailwindcss/postcss": "^4.1.13", + "@tailwindcss/postcss": "^4.1.16", "@types/debounce-promise": "^3.1.9", "@types/lodash.debounce": "^4.0.9", "@types/lodash.throttle": "^4.1.9", - "@types/node": "^24.5.2", + "@types/node": "^24.9.2", "@types/react": "19.2.2", "@types/react-dom": "19.2.2", "@types/react-helmet": "^6.1.11", - "@typescript-eslint/eslint-plugin": "^8.44.1", - "@typescript-eslint/parser": "^8.44.1", + "@typescript-eslint/eslint-plugin": "^8.46.2", + "@typescript-eslint/parser": "^8.46.2", "autoprefixer": "^10.4.21", - "dotenv": "^17.2.2", - "dotenv-cli": "^10.0.0", - "eslint": "^9.36.0", + "buffer": "^6.0.3", + "crypto-browserify": "^3.12.1", + "dotenv": "^17.2.3", + "dotenv-cli": "^11.0.0", + "eslint": "^9.38.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-functional": "^9.0.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-prettier": "^5.5.4", "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-hooks": "^7.0.1", "husky": "^9.1.7", "identity-obj-proxy": "^3.0.0", - "lint-staged": "^16.2.0", + "lint-staged": "^16.2.6", "prettier": "^3.6.2", - "prettier-plugin-tailwindcss": "^0.6.14", + "prettier-plugin-tailwindcss": "^0.7.1", + "react-native-web": "^0.21.2", "shelljs": "^0.10.0", + "stream-browserify": "^3.0.0", "tailwind-scrollbar-hide": "^4.0.0", "tailwindcss": "3.4.1", - "typescript": "^5.9.2", - "typescript-eslint": "^8.44.1" + "typescript": "^5.9.3", + "typescript-eslint": "^8.46.2" }, "overrides": { "chalk": "5.3.0", diff --git a/src/api/campaign/getCampaignApys.ts b/src/api/campaign/getCampaignApys.ts index 69067d27a..a7c9ed7f9 100644 --- a/src/api/campaign/getCampaignApys.ts +++ b/src/api/campaign/getCampaignApys.ts @@ -3,8 +3,10 @@ import { fetchWithTimeout } from 'utils/fetch' import { convertAprToApy } from 'utils/parsers' function processApyData(aprOrApy: number, isApr: boolean, isPercent: boolean): number { - if (!isApr && isPercent) return aprOrApy - const percentApr = isPercent ? aprOrApy : aprOrApy * 100 + // Ensure the value is a number (API might return strings) + const numericValue = typeof aprOrApy === 'string' ? parseFloat(aprOrApy) : aprOrApy + if (!isApr && isPercent) return numericValue + const percentApr = isPercent ? numericValue : numericValue * 100 const apy = isApr ? convertAprToApy(percentApr, 365) : percentApr return apy } diff --git a/src/chains/neutron/neutron-1.ts b/src/chains/neutron/neutron-1.ts index 01fef1b84..5143ea01f 100644 --- a/src/chains/neutron/neutron-1.ts +++ b/src/chains/neutron/neutron-1.ts @@ -118,6 +118,11 @@ const Neutron1: ChainConfig = { campaignIds: ['lido'], campaignDenom: 'stETH', }, + { + denom: 'ibc/0E293A7622DC9A6439DB60E6D234B5AF446962E27CA3AB44D0590603DFF6968E', + campaignIds: ['ntrn-rewards'], + campaignDenom: 'wbtc', + }, ], deprecated: [ 'ibc/3649CE0C8A2C79048D8C6F31FF18FA69C9BC7EB193512E0BD03B733011290445', diff --git a/src/components/Modals/Farm/FarmBorrowings.tsx b/src/components/Modals/Farm/FarmBorrowings.tsx index 86e983ff1..f1d42469b 100644 --- a/src/components/Modals/Farm/FarmBorrowings.tsx +++ b/src/components/Modals/Farm/FarmBorrowings.tsx @@ -130,8 +130,9 @@ export default function FarmBorrowings(props: FarmBorrowingsProps) { function updateAssets(denom: string, amount: BigNumber) { const index = props.borrowings.findIndex((coin) => coin.denom === denom) - props.borrowings[index].amount = amount - props.onChangeBorrowings([...props.borrowings]) + const newBorrowings = [...props.borrowings] + newBorrowings[index].amount = amount + props.onChangeBorrowings(newBorrowings) } function onDelete(denom: string) { diff --git a/src/components/Modals/Farm/FarmDeposits.tsx b/src/components/Modals/Farm/FarmDeposits.tsx index 8c305d1dd..d5c8c2c8c 100644 --- a/src/components/Modals/Farm/FarmDeposits.tsx +++ b/src/components/Modals/Farm/FarmDeposits.tsx @@ -163,9 +163,9 @@ export default function FarmDeposits(props: Props) { function handleSwitch() { const isCustomRatioNew = !props.isCustomRatio if (!isCustomRatioNew) { - primaryCoin.amount = BN_ZERO - secondaryCoin.amount = BN_ZERO - onChangeDeposits([primaryCoin, secondaryCoin]) + const newPrimaryCoin = new BNCoin({ denom: primaryCoin.denom, amount: '0' }) + const newSecondaryCoin = new BNCoin({ denom: secondaryCoin.denom, amount: '0' }) + onChangeDeposits([newPrimaryCoin, newSecondaryCoin]) setPercentage(0) } props.onChangeIsCustomRatio(isCustomRatioNew) @@ -176,15 +176,17 @@ export default function FarmDeposits(props: Props) { if (amount.isGreaterThan(primaryMax)) { amount = primaryMax } - primaryCoin.amount = amount + const newPrimaryCoin = new BNCoin({ denom: primaryCoin.denom, amount: amount.toString() }) setPercentage(amount.dividedBy(primaryMax).multipliedBy(100).decimalPlaces(0).toNumber()) - if (!props.isCustomRatio) { - secondaryCoin.amount = secondaryMax - .multipliedBy(amount.dividedBy(primaryMax)) - .integerValue() - } + const newSecondaryAmount = !props.isCustomRatio + ? secondaryMax.multipliedBy(amount.dividedBy(primaryMax)).integerValue() + : secondaryCoin.amount + const newSecondaryCoin = new BNCoin({ + denom: secondaryCoin.denom, + amount: newSecondaryAmount.toString(), + }) - onChangeDeposits([primaryCoin, secondaryCoin]) + onChangeDeposits([newPrimaryCoin, newSecondaryCoin]) }, [primaryMax, secondaryMax, props.isCustomRatio, primaryCoin, secondaryCoin, onChangeDeposits], ) @@ -194,13 +196,20 @@ export default function FarmDeposits(props: Props) { if (amount.isGreaterThan(secondaryMax)) { amount = secondaryMax } - secondaryCoin.amount = amount + const newSecondaryCoin = new BNCoin({ + denom: secondaryCoin.denom, + amount: amount.toString(), + }) setPercentage(amount.dividedBy(secondaryMax).multipliedBy(100).decimalPlaces(0).toNumber()) - if (!props.isCustomRatio) { - primaryCoin.amount = primaryMax.multipliedBy(amount.dividedBy(secondaryMax)).integerValue() - } + const newPrimaryAmount = !props.isCustomRatio + ? primaryMax.multipliedBy(amount.dividedBy(secondaryMax)).integerValue() + : primaryCoin.amount + const newPrimaryCoin = new BNCoin({ + denom: primaryCoin.denom, + amount: newPrimaryAmount.toString(), + }) - onChangeDeposits([primaryCoin, secondaryCoin]) + onChangeDeposits([newPrimaryCoin, newSecondaryCoin]) }, [primaryMax, secondaryMax, props.isCustomRatio, primaryCoin, secondaryCoin, onChangeDeposits], ) @@ -208,9 +217,15 @@ export default function FarmDeposits(props: Props) { const onChangeSlider = useCallback( (value: number) => { if (percentage !== value) setPercentage(value) - primaryCoin.amount = primaryMax.multipliedBy(value / 100).integerValue() - secondaryCoin.amount = secondaryMax.multipliedBy(value / 100).integerValue() - onChangeDeposits([primaryCoin, secondaryCoin]) + const newPrimaryCoin = new BNCoin({ + denom: primaryCoin.denom, + amount: primaryMax.multipliedBy(value / 100).integerValue().toString(), + }) + const newSecondaryCoin = new BNCoin({ + denom: secondaryCoin.denom, + amount: secondaryMax.multipliedBy(value / 100).integerValue().toString(), + }) + onChangeDeposits([newPrimaryCoin, newSecondaryCoin]) }, [percentage, primaryCoin, primaryMax, secondaryCoin, secondaryMax, onChangeDeposits], ) diff --git a/src/components/account/AccountBalancesTable/Columns/Apy.tsx b/src/components/account/AccountBalancesTable/Columns/Apy.tsx index c3fccd2d2..5d7072972 100644 --- a/src/components/account/AccountBalancesTable/Columns/Apy.tsx +++ b/src/components/account/AccountBalancesTable/Columns/Apy.tsx @@ -28,7 +28,7 @@ export default function Apy(props: Props) { return (