diff --git a/.yarn/cache/@kurkle-color-npm-0.3.2-98f2086013-079c4b7688.zip b/.yarn/cache/@kurkle-color-npm-0.3.2-98f2086013-079c4b7688.zip
new file mode 100644
index 000000000..3972727bd
Binary files /dev/null and b/.yarn/cache/@kurkle-color-npm-0.3.2-98f2086013-079c4b7688.zip differ
diff --git a/.yarn/cache/chart.js-npm-4.4.2-e9b1497a6e-609444dfc9.zip b/.yarn/cache/chart.js-npm-4.4.2-e9b1497a6e-609444dfc9.zip
new file mode 100644
index 000000000..5774a9780
Binary files /dev/null and b/.yarn/cache/chart.js-npm-4.4.2-e9b1497a6e-609444dfc9.zip differ
diff --git a/.yarn/cache/react-chartjs-2-npm-5.2.0-03632f5179-0a70b60e1a.zip b/.yarn/cache/react-chartjs-2-npm-5.2.0-03632f5179-0a70b60e1a.zip
new file mode 100644
index 000000000..30cf53f05
Binary files /dev/null and b/.yarn/cache/react-chartjs-2-npm-5.2.0-03632f5179-0a70b60e1a.zip differ
diff --git a/liquidity/lib/useBlockchain/useBlockchain.tsx b/liquidity/lib/useBlockchain/useBlockchain.tsx
index b91cbed5e..027b8f64d 100644
--- a/liquidity/lib/useBlockchain/useBlockchain.tsx
+++ b/liquidity/lib/useBlockchain/useBlockchain.tsx
@@ -1,5 +1,5 @@
import { ethers } from 'ethers';
-import React from 'react';
+import React, { useMemo } from 'react';
import {
BaseIcon,
EthereumIcon,
@@ -251,6 +251,12 @@ export function useWallet() {
};
}
+export function useGetNetwork(chainId: string) {
+ return useMemo(() => {
+ return NETWORKS.find((n) => n.hexId === chainId);
+ }, [chainId]);
+}
+
export function useNetwork() {
const [{ connectedChain }, setChain] = useSetChain();
diff --git a/oracle-manager/ui/.env.example b/oracle-manager/ui/.env.example
index ddaf0d78a..e3c40a8bb 100644
--- a/oracle-manager/ui/.env.example
+++ b/oracle-manager/ui/.env.example
@@ -1 +1,2 @@
NEXT_PUBLIC_WC_PROJECT_ID=xxx
+NEXT_PUBLIC_INFURA_KEY=xxx
diff --git a/ultrasound/ui/.env.example b/ultrasound/ui/.env.example
new file mode 100644
index 000000000..588b0d714
--- /dev/null
+++ b/ultrasound/ui/.env.example
@@ -0,0 +1,3 @@
+INFURA_KEY=xxx
+PYTH_MAINNET_ENDPOINT=https://hermes.pyth.network/
+PYTH_TESTNET_ENDPOINT=https://hermes.pyth.network/
diff --git a/ultrasound/ui/README.md b/ultrasound/ui/README.md
new file mode 100644
index 000000000..39eaa9d11
--- /dev/null
+++ b/ultrasound/ui/README.md
@@ -0,0 +1 @@
+# ULTRASOUND HOMES
diff --git a/ultrasound/ui/babel.config.js b/ultrasound/ui/babel.config.js
new file mode 100644
index 000000000..245cf461d
--- /dev/null
+++ b/ultrasound/ui/babel.config.js
@@ -0,0 +1,68 @@
+const path = require('path');
+require.resolve('core-js');
+require.resolve('@babel/runtime-corejs3/core-js/date/now');
+
+module.exports = {
+ presets: [
+ require.resolve('@babel/preset-typescript'),
+ [require.resolve('@babel/preset-react'), { runtime: 'automatic' }],
+ ],
+
+ plugins: [[require.resolve('@babel/plugin-transform-runtime'), { corejs: 3 }]],
+
+ env: {
+ production: {
+ presets: [
+ [
+ require.resolve('@babel/preset-env'),
+ {
+ useBuiltIns: 'usage',
+ corejs: { version: 3, proposals: true },
+ modules: false,
+ targets: {
+ browsers: require('./package.json').browserslist,
+ },
+ },
+ ],
+ ],
+ },
+
+ development: {
+ presets: [
+ [
+ require.resolve('@babel/preset-env'),
+ {
+ modules: false,
+ targets: { browsers: ['last 1 Chrome version'] },
+ },
+ ],
+ ],
+ plugins: [require.resolve('react-refresh/babel')],
+ },
+
+ test: {
+ presets: [
+ [
+ require.resolve('@babel/preset-env'),
+ {
+ modules: 'commonjs',
+ targets: { node: 'current' },
+ },
+ ],
+ ],
+ plugins: [
+ [
+ require.resolve('babel-plugin-istanbul'),
+ {
+ cwd: path.resolve('../..'),
+ all: true,
+ excludeNodeModules: false,
+ include: ['v3'],
+ exclude: ['**/*.test.*', '**/*.cy.*', '**/*.e2e.*'],
+ },
+ 'istanbul',
+ ],
+ ],
+ },
+ },
+};
diff --git a/ultrasound/ui/components/BurnSNX.tsx b/ultrasound/ui/components/BurnSNX.tsx
new file mode 100644
index 000000000..7bb64bc84
--- /dev/null
+++ b/ultrasound/ui/components/BurnSNX.tsx
@@ -0,0 +1,62 @@
+import { Button, Flex, Image, Link, Text, Tooltip, useDisclosure } from '@chakra-ui/react';
+import { BurnSNXModal } from './BurnSNXModal';
+import { isBaseAndromeda } from '@snx-v3/isBaseAndromeda';
+import { useNetwork } from '@snx-v3/useBlockchain';
+import { useSNXPrice } from '../hooks/useSNXPrice';
+
+export function BurnSNX() {
+ const { network } = useNetwork();
+ const { isOpen, onOpen, onClose } = useDisclosure();
+ const { data: SNXPrice } = useSNXPrice();
+
+ return (
+
+
+
+
+ Sell SNX at premium and watch it burn
+
+
+ Sell your SNX at a premium price to the Buyback and Burn contract and get USDC on
+ Base
+
+
+ {SNXPrice?.eq(0) ? (
+ 'refecthing...'
+ ) : (
+ <>
+ Buyback Price: $ {SNXPrice?.toNumber().toFixed(2)} $
+ {SNXPrice ? (SNXPrice?.toNumber() + SNXPrice?.toNumber() * 0.01).toFixed(2) : 0}
+ >
+ )}
+
+
+ {!isBaseAndromeda(network?.id, network?.preset) ? (
+
+
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+ );
+}
diff --git a/ultrasound/ui/components/BurnSNXModal.tsx b/ultrasound/ui/components/BurnSNXModal.tsx
new file mode 100644
index 000000000..b2fdf6d40
--- /dev/null
+++ b/ultrasound/ui/components/BurnSNXModal.tsx
@@ -0,0 +1,205 @@
+import {
+ Button,
+ Divider,
+ Flex,
+ Image,
+ Input,
+ Modal,
+ ModalBody,
+ ModalCloseButton,
+ ModalContent,
+ ModalHeader,
+ ModalOverlay,
+ Spinner,
+ Text,
+} from '@chakra-ui/react';
+import { useWallet } from '@snx-v3/useBlockchain';
+import { useTokenBalance } from '@snx-v3/useTokenBalance';
+import { SNXUSDBalanceOfBuyBackContract } from '../hooks/SNXUSDBalanceOfBuyBackContract';
+import { useApprove } from '@snx-v3/useApprove';
+import Wei from '@synthetixio/wei';
+import { useState } from 'react';
+import { useSellSNX } from '../mutations/useSellSNX';
+import { useBurnEvents } from '../hooks/useBurnEvents';
+import { useSNXPrice } from '../hooks/useSNXPrice';
+
+export function BurnSNXModal({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) {
+ const [amount, setAmount] = useState(new Wei(0));
+ const [receivingUSDCAmount, setReceivingUSDCAmount] = useState(0);
+ const { data: events } = useBurnEvents();
+ const { connect, activeWallet } = useWallet();
+ const { data: SNXPrice } = useSNXPrice();
+
+ const { data: snxBalance } = useTokenBalance('0x22e6966B799c4D5B13BE962E1D117b56327FDa66');
+ const { data: usdcBalance } = useTokenBalance('0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913');
+ const { data: contractBalance } = SNXUSDBalanceOfBuyBackContract(
+ '0x632cAa10A56343C5e6C0c066735840c096291B18'
+ );
+
+ const { requireApproval, approve, refetchAllowance } = useApprove({
+ contractAddress: '0x22e6966B799c4D5B13BE962E1D117b56327FDa66',
+ amount: amount ? amount.toBN() : 0,
+ spender: '0x632cAa10A56343C5e6C0c066735840c096291B18',
+ });
+ const { mutateAsync, isPending } = useSellSNX();
+
+ return (
+
+
+
+
+ Burn your SNX
+
+
+
+
+
+
+ Burn
+
+
+
+
+
+
+ SNX
+
+
+ {
+ try {
+ if (SNXPrice) {
+ const snxAmount = new Wei(e.target.value);
+ setAmount(snxAmount);
+ setReceivingUSDCAmount(
+ snxAmount.mul(SNXPrice).add(SNXPrice.mul(0.01)).toNumber()
+ );
+ }
+ } catch (error) {
+ console.error('failed to parse input: ', Error);
+ setAmount(undefined);
+ }
+ }}
+ />
+
+
+ {
+ if (SNXPrice && snxBalance) {
+ setAmount(snxBalance);
+ setReceivingUSDCAmount(
+ snxBalance.mul(SNXPrice).add(SNXPrice.mul(0.01)).toNumber()
+ );
+ }
+ }}
+ >
+ Balance: {snxBalance ? snxBalance.toNumber().toFixed(2) : '-'}
+
+
+ $
+
+
+
+ max burnable:{' '}
+ {contractBalance &&
+ events?.SNXPrice &&
+ (
+ new Wei(contractBalance, 18).toNumber() /
+ (events.SNXPrice + events.SNXPrice * 0.01)
+ ).toFixed(2)}
+
+
+ Receive
+
+
+
+
+
+ USDC
+
+
+
+
+
+
+ Balance: {usdcBalance ? usdcBalance.toNumber().toFixed(2) : '-'}
+
+
+
+ {isPending ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+}
diff --git a/ultrasound/ui/components/Chart.tsx b/ultrasound/ui/components/Chart.tsx
new file mode 100644
index 000000000..49d9036ec
--- /dev/null
+++ b/ultrasound/ui/components/Chart.tsx
@@ -0,0 +1,82 @@
+import { Flex, Spinner, Tab, TabList, Tabs } from '@chakra-ui/react';
+import { useBurnEvents } from '../hooks/useBurnEvents';
+import { Line } from 'react-chartjs-2';
+import {
+ Chart as ChartJS,
+ CategoryScale,
+ LinearScale,
+ PointElement,
+ LineElement,
+ Title,
+ Tooltip,
+ Legend,
+} from 'chart.js';
+import { useState } from 'react';
+
+ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend);
+
+export function Chart() {
+ const [range, setRange] = useState<'groupedByMonths' | 'groupedByLast30Days'>('groupedByMonths');
+ const { data: events, isLoading } = useBurnEvents();
+
+ if (isLoading) return ;
+ if (!events) return;
+ return (
+
+
+
+
+ setRange('groupedByLast30Days')}
+ color="gray.500"
+ _selected={{ color: 'white', bg: 'whiteAlpha.400' }}
+ >
+ 1M
+
+ setRange('groupedByMonths')}
+ color="gray.500"
+ _selected={{ color: 'white', bg: 'whiteAlpha.400' }}
+ >
+ All
+
+
+
+
+ );
+}
diff --git a/ultrasound/ui/components/CurrentSupplyStats.tsx b/ultrasound/ui/components/CurrentSupplyStats.tsx
new file mode 100644
index 000000000..3e5045e90
--- /dev/null
+++ b/ultrasound/ui/components/CurrentSupplyStats.tsx
@@ -0,0 +1,46 @@
+import { Flex, Spinner, Text } from '@chakra-ui/react';
+import { useBurnEvents } from '../hooks/useBurnEvents';
+
+export function CurrentSupplyStats() {
+ const { data: events, isLoading } = useBurnEvents();
+
+ return (
+
+ {isLoading ? (
+
+ ) : (
+ <>
+
+
+ Curren tSupply
+
+
+ ALL
+
+
+
+ {events?.totalSupply} SNX
+
+ >
+ )}
+
+ );
+}
diff --git a/ultrasound/ui/components/Header.tsx b/ultrasound/ui/components/Header.tsx
new file mode 100644
index 000000000..7cd4eafcc
--- /dev/null
+++ b/ultrasound/ui/components/Header.tsx
@@ -0,0 +1,69 @@
+import { Box, Button, Divider, Flex, Image, Text, useColorMode } from '@chakra-ui/react';
+import { useIsConnected, useNetwork, useWallet } from '@snx-v3/useBlockchain';
+import { shortAddress } from '../utils/addresses';
+import { NetworkSelect } from './NetworkSelect';
+import { useTokenBalance } from '@snx-v3/useTokenBalance';
+import { useEffect } from 'react';
+
+export function Header() {
+ const { colorMode, toggleColorMode } = useColorMode();
+ const { network, setNetwork } = useNetwork();
+ const isWalletConnected = useIsConnected();
+ const { activeWallet, connect, disconnect, walletsInfo } = useWallet();
+ const { data: snxBalance } = useTokenBalance('0x22e6966B799c4D5B13BE962E1D117b56327FDa66');
+
+ useEffect(() => {
+ if (colorMode === 'light') {
+ toggleColorMode();
+ }
+ }, [colorMode, toggleColorMode]);
+
+ return (
+
+
+
+
+
+
+
+ {snxBalance ? snxBalance?.toNumber().toFixed(2) : '-'}
+
+
+ {isWalletConnected && (
+ setNetwork(netowork.id)}
+ />
+ )}
+
+ {isWalletConnected ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+}
diff --git a/ultrasound/ui/components/Main.tsx b/ultrasound/ui/components/Main.tsx
new file mode 100644
index 000000000..1d3e89f63
--- /dev/null
+++ b/ultrasound/ui/components/Main.tsx
@@ -0,0 +1,77 @@
+import { Flex, Heading, Image, Link, Spinner, Text } from '@chakra-ui/react';
+import { Chart } from './Chart';
+import { SupplyChangeStats } from './SupplyChangeStats';
+import { CurrentSupplyStats } from './CurrentSupplyStats';
+import { BurnSNX } from './BurnSNX';
+import { useBurnEvents } from '../hooks/useBurnEvents';
+
+export function Main() {
+ const { data: events, isLoading } = useBurnEvents();
+
+ return (
+
+
+
+
+ ultrasound.homes
+
+
+ burning SNX for Kain's mansions
+
+
+
+
+
+
+ Mansion counter
+
+
+ {isLoading ? : events?.totalBurns}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Share
+
+
+
+
+
+
+ );
+}
diff --git a/ultrasound/ui/components/NetworkSelect.tsx b/ultrasound/ui/components/NetworkSelect.tsx
new file mode 100644
index 000000000..365e5fb34
--- /dev/null
+++ b/ultrasound/ui/components/NetworkSelect.tsx
@@ -0,0 +1,50 @@
+import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons';
+import { Button, Menu, MenuButton, MenuItem, MenuList, Text } from '@chakra-ui/react';
+import { NETWORKS, NetworkIcon } from '@snx-v3/useBlockchain';
+
+export function NetworkSelect({
+ id,
+ name,
+ setNetwork,
+}: {
+ id: string | number;
+ name: string;
+ setNetwork: ({ id, name }: { id: number; name: string }) => any;
+}) {
+ return (
+
+ );
+}
diff --git a/ultrasound/ui/components/SupplyChangeStats.tsx b/ultrasound/ui/components/SupplyChangeStats.tsx
new file mode 100644
index 000000000..107050f9f
--- /dev/null
+++ b/ultrasound/ui/components/SupplyChangeStats.tsx
@@ -0,0 +1,86 @@
+import { Flex, Image, Link, Spinner, Text, Tooltip } from '@chakra-ui/react';
+import { useBurnEvents } from '../hooks/useBurnEvents';
+import { InfoIcon } from '@chakra-ui/icons';
+
+export function SupplyChangeStats() {
+ const { data: events, isLoading } = useBurnEvents();
+
+ return (
+
+ {isLoading ? (
+
+ ) : (
+ <>
+
+
+ Supply Change
+
+
+ 7D
+
+
+
+ -{events?.supplyChange7Days.toString()} SNX
+
+
+
+
+ Burnt
+
+
+ {events?.supplyChange7Days.toString()} SNX
+
+
+
+
+
+ Minted
+
+
+ 0.00 SNX
+
+
+
+ No more SNX are being minted. Read more about The End of Synthetix Token Inflation
+
+ }
+ >
+
+
+
+ Why?
+
+
+
+
+ >
+ )}
+
+ );
+}
diff --git a/ultrasound/ui/favicon.ico b/ultrasound/ui/favicon.ico
new file mode 100644
index 000000000..5845395c4
Binary files /dev/null and b/ultrasound/ui/favicon.ico differ
diff --git a/ultrasound/ui/hooks/SNXUSDBalanceOfBuyBackContract.ts b/ultrasound/ui/hooks/SNXUSDBalanceOfBuyBackContract.ts
new file mode 100644
index 000000000..e63a6bacd
--- /dev/null
+++ b/ultrasound/ui/hooks/SNXUSDBalanceOfBuyBackContract.ts
@@ -0,0 +1,18 @@
+import { useQuery } from '@tanstack/react-query';
+import { BigNumber, Contract, providers } from 'ethers';
+
+const USDC = new Contract(
+ '0x09d51516F38980035153a554c26Df3C6f51a23C3',
+ ['function balanceOf(address _owner) view returns (uint256 balance)'],
+ new providers.JsonRpcProvider('https://base.llamarpc.com')
+);
+
+export function SNXUSDBalanceOfBuyBackContract(contract: string) {
+ return useQuery({
+ queryKey: ['USDCBalanceOfBuyBackContract'],
+ queryFn: async () => {
+ const balance: BigNumber = await USDC.balanceOf(contract);
+ return balance;
+ },
+ });
+}
diff --git a/ultrasound/ui/hooks/useBurnEvents.ts b/ultrasound/ui/hooks/useBurnEvents.ts
new file mode 100644
index 000000000..511dfc2ea
--- /dev/null
+++ b/ultrasound/ui/hooks/useBurnEvents.ts
@@ -0,0 +1,86 @@
+import { useQuery } from '@tanstack/react-query';
+import { BigNumber, Contract, providers, utils } from 'ethers';
+
+interface BurnEvent {
+ ts: number;
+ snxAmount: number;
+ usdAmount: number;
+ cumulativeSnxAmount: number;
+ cumulativeUsdAmount: number;
+}
+
+const SNXonL1 = new Contract(
+ '0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F',
+ ['function totalSupply() view returns(uint256)'],
+ new providers.JsonRpcProvider('https://eth.llamarpc.com')
+);
+
+const now = new Date();
+now.setDate(now.getDate() - 7);
+const thirtyDaysAgo = new Date();
+thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
+
+export function useBurnEvents() {
+ return useQuery({
+ queryKey: ['burn-events'],
+ queryFn: async () => {
+ const repsonse = await fetch('https://api.synthetix.io/v3/base/snx-buyback');
+ const events: BurnEvent[] = await repsonse.json();
+
+ const totalSupply: BigNumber = await SNXonL1.totalSupply();
+
+ const supplyChange7Days = events
+ .filter((event) => event.ts > now.getTime())
+ .reduce((cur, prev) => cur + prev.snxAmount, 0)
+ .toFixed(2);
+
+ const SNXPriceResponse = await fetch(
+ 'https://coins.llama.fi/prices/current/ethereum:0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F'
+ );
+
+ const { coins } = await SNXPriceResponse.json();
+
+ const groupedByMonths = events.reduce(
+ (cur, prev) => {
+ const currentDate = new Date(prev.ts);
+ const month = currentDate.toLocaleString('default', { month: 'long', year: 'numeric' });
+ if (cur[month]) {
+ cur[month] = cur[month] - prev.snxAmount;
+ } else {
+ cur[month] = Number(utils.formatEther(totalSupply)) - prev.cumulativeSnxAmount;
+ }
+ return cur;
+ },
+ {} as Record
+ );
+
+ const groupedByLast30Days = events.reduce(
+ (cur, prev) => {
+ const currentDate = new Date(prev.ts);
+ if (currentDate.getTime() > thirtyDaysAgo.getTime()) {
+ const day = currentDate.toLocaleString('default', { day: '2-digit', month: 'long' });
+ if (cur[day]) {
+ cur[day] = cur[day] - prev.snxAmount;
+ } else {
+ cur[day] = Number(utils.formatEther(totalSupply)) - prev.cumulativeSnxAmount;
+ }
+ }
+ return cur;
+ },
+ {} as Record
+ );
+
+ return {
+ totalBurns: events.length,
+ groupedByMonths,
+ groupedByLast30Days,
+ supplyChange7Days,
+ totalSupply: Number(utils.formatEther(totalSupply)).toLocaleString('en-US', {
+ maximumFractionDigits: 2,
+ }),
+ SNXPrice:
+ (coins['ethereum:0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F'].price as number) || 0,
+ };
+ },
+ });
+}
diff --git a/ultrasound/ui/hooks/useSNXPrice.ts b/ultrasound/ui/hooks/useSNXPrice.ts
new file mode 100644
index 000000000..8af4620fb
--- /dev/null
+++ b/ultrasound/ui/hooks/useSNXPrice.ts
@@ -0,0 +1,57 @@
+import { useQuery } from '@tanstack/react-query';
+import { useGetNetwork, useProviderForChain } from '@snx-v3/useBlockchain';
+import { erc7412Call } from '@snx-v3/withERC7412';
+import { importOracleManagerProxy, OracleManagerProxyType } from '@synthetixio/v3-contracts';
+import { Contract } from 'ethers';
+import { Wei } from '@synthetixio/wei';
+import { BuyBack } from '../mutations/useSellSNX';
+
+export function useSNXPrice() {
+ const baseNetwork = useGetNetwork(`0x${Number(8453).toString(16)}`);
+ const baseProvider = useProviderForChain(baseNetwork);
+
+ return useQuery({
+ refetchInterval: 5000,
+ enabled: !!baseProvider,
+ queryKey: ['snx-price', !!baseProvider],
+ queryFn: async () => {
+ if (baseProvider && baseNetwork?.id && baseNetwork?.preset) {
+ try {
+ const { address, abi } = await importOracleManagerProxy(
+ baseNetwork.id,
+ baseNetwork.preset
+ );
+ const OracleManagerProxy = new Contract(
+ address,
+ abi,
+ baseProvider
+ ) as OracleManagerProxyType;
+
+ const price = [
+ await OracleManagerProxy.populateTransaction.process(
+ await BuyBack.connect(baseProvider).getSnxNodeId()
+ ),
+ ];
+
+ price[0].from = '0x4200000000000000000000000000000000000006';
+
+ return await erc7412Call(
+ baseNetwork,
+ baseProvider,
+ price,
+ (txs) => {
+ return new Wei(
+ OracleManagerProxy.interface.decodeFunctionResult('process', txs[0])[0].price
+ );
+ },
+ 'priceCall'
+ );
+ } catch (error) {
+ console.error(error);
+ return new Wei(0);
+ }
+ }
+ return new Wei(0);
+ },
+ });
+}
diff --git a/ultrasound/ui/index.html b/ultrasound/ui/index.html
new file mode 100644
index 000000000..ad2fb62a7
--- /dev/null
+++ b/ultrasound/ui/index.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+ Ultrasound Homes
+
+
+
+
+
+
diff --git a/ultrasound/ui/load-env.sh b/ultrasound/ui/load-env.sh
new file mode 100755
index 000000000..15734fe01
--- /dev/null
+++ b/ultrasound/ui/load-env.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+set -a
+source .env
+set +a
+
+echo "Loaded Environment Variables:"
+env | grep "NEXT"
\ No newline at end of file
diff --git a/ultrasound/ui/mutations/useSellSNX.ts b/ultrasound/ui/mutations/useSellSNX.ts
new file mode 100644
index 000000000..56ced1546
--- /dev/null
+++ b/ultrasound/ui/mutations/useSellSNX.ts
@@ -0,0 +1,94 @@
+import { useMutation } from '@tanstack/react-query';
+import { useCoreProxy } from '@snx-v3/useCoreProxy';
+import { useUSDProxy } from '@snx-v3/useUSDProxy';
+import { useSpotMarketProxy } from '@snx-v3/useSpotMarketProxy';
+import Wei from '@synthetixio/wei';
+import { Contract, constants, utils } from 'ethers';
+import { getGasPrice } from '@snx-v3/useGasPrice';
+import { useDefaultProvider, useNetwork, useSigner } from '@snx-v3/useBlockchain';
+import { withERC7412 } from '@snx-v3/withERC7412';
+import { formatGasPriceForTransaction } from '@snx-v3/useGasOptions';
+import { useGasSpeed } from '@snx-v3/useGasSpeed';
+import { notNil } from '@snx-v3/tsHelpers';
+import { useSNXPrice } from '../hooks/useSNXPrice';
+
+export const BuyBack = new Contract('0x632cAa10A56343C5e6C0c066735840c096291B18', [
+ 'function processBuyback(uint256 snxAmount) external',
+ 'function getPremium() view returns(uint256)',
+ 'function getSnxNodeId() view returns(bytes32)',
+]);
+
+export function useSellSNX() {
+ const { data: CoreProxy } = useCoreProxy();
+ const { data: UsdProxy } = useUSDProxy();
+ const { data: SpotProxy } = useSpotMarketProxy();
+ const provider = useDefaultProvider();
+ const { network } = useNetwork();
+ const signer = useSigner();
+ const { gasSpeed } = useGasSpeed();
+ const { data: SNXPrice, refetch } = useSNXPrice();
+
+ return useMutation({
+ mutationKey: ['sell-snx'],
+ mutationFn: async (amount: Wei) => {
+ await refetch();
+ if (!CoreProxy) return;
+ if (!network) return;
+ if (!SpotProxy) return;
+ if (!signer) return;
+ if (!SNXPrice) return;
+ if (!provider) return;
+ if (!SNXPrice) return;
+
+ const gasPricesPromised = getGasPrice({ provider });
+
+ const premium = await BuyBack.connect(signer).getPremium();
+
+ const USDCAmountPlusPremium = SNXPrice.mul(amount).add(SNXPrice.mul(premium));
+
+ const sellSNX = BuyBack.connect(signer).populateTransaction.processBuyback(amount.toBN());
+
+ const snxUSDApproval = UsdProxy?.populateTransaction.approve(
+ SpotProxy.address,
+ USDCAmountPlusPremium.toBN()
+ );
+
+ const buy_SUSD = SpotProxy.populateTransaction.buy(
+ 1,
+ USDCAmountPlusPremium.toBN(),
+ 0,
+ constants.AddressZero
+ );
+
+ const unwrapTxnPromised = SpotProxy.populateTransaction.unwrap(
+ 1,
+ USDCAmountPlusPremium.toBN(),
+ //2% slippage
+ Number(
+ utils.formatUnits(USDCAmountPlusPremium.toBN().mul(99).div(100).toString(), 12).toString()
+ ).toFixed()
+ );
+ const [gasPrices, sellSNX_Txn, sUSDCApproval_Txn, buy_SUSD_Txn, unwrapTxn] =
+ await Promise.all([
+ gasPricesPromised,
+ sellSNX,
+ snxUSDApproval,
+ buy_SUSD,
+ unwrapTxnPromised,
+ ]);
+
+ const allCalls = [sellSNX_Txn, sUSDCApproval_Txn, buy_SUSD_Txn, unwrapTxn].filter(notNil);
+
+ const erc7412Tx = await withERC7412(network, allCalls, 'useWithdraw', CoreProxy.interface);
+
+ const gasOptionsForTransaction = formatGasPriceForTransaction({
+ gasLimit: erc7412Tx.gasLimit,
+ gasPrices,
+ gasSpeed,
+ });
+
+ const txn = await signer.sendTransaction({ ...erc7412Tx, ...gasOptionsForTransaction });
+ await txn.wait();
+ },
+ });
+}
diff --git a/ultrasound/ui/package.json b/ultrasound/ui/package.json
new file mode 100644
index 000000000..861904785
--- /dev/null
+++ b/ultrasound/ui/package.json
@@ -0,0 +1,92 @@
+{
+ "name": "@snx-v3/ultrasound-homes",
+ "version": "0.0.1",
+ "private": true,
+ "scripts": {
+ "start": "webpack-cli serve",
+ "build": "NODE_ENV=production webpack-cli",
+ "load-env": "source load-env.sh",
+ "focus": "yarn workspaces focus '@snx-v3/ultrasound-homes'"
+ },
+ "dependencies": {
+ "@chakra-ui/icons": "^2.1.1",
+ "@chakra-ui/react": "^2.8.2",
+ "@emotion/react": "^11.11.1",
+ "@emotion/styled": "^11.11.0",
+ "@snx-v3/isBaseAndromeda": "workspace:*",
+ "@snx-v3/tsHelpers": "workspace:*",
+ "@snx-v3/useApprove": "workspace:*",
+ "@snx-v3/useBlockchain": "workspace:*",
+ "@snx-v3/useCoreProxy": "workspace:*",
+ "@snx-v3/useGasOptions": "workspace:*",
+ "@snx-v3/useGasPrice": "workspace:*",
+ "@snx-v3/useGasSpeed": "workspace:*",
+ "@snx-v3/useSpotMarketProxy": "workspace:*",
+ "@snx-v3/useTokenBalance": "workspace:*",
+ "@snx-v3/useUSDProxy": "workspace:*",
+ "@snx-v3/withERC7412": "workspace:*",
+ "@synthetixio/v3-contracts": "workspace:*",
+ "@synthetixio/v3-theme": "workspace:*",
+ "@synthetixio/wei": "^2.74.4",
+ "@tanstack/react-query": "^5.8.3",
+ "@tanstack/react-query-devtools": "^5.8.3",
+ "@web3-onboard/coinbase": "^2.2.6",
+ "@web3-onboard/injected-wallets": "^2.10.11",
+ "@web3-onboard/ledger": "^2.5.2",
+ "@web3-onboard/react": "^2.8.13",
+ "@web3-onboard/trezor": "^2.4.3",
+ "@web3-onboard/walletconnect": "^2.5.3",
+ "chart.js": "^4.0.0",
+ "ethers": "^5.7.2",
+ "framer-motion": "^10.16.5",
+ "react": "^18.2.0",
+ "react-chartjs-2": "^5.2.0",
+ "react-dom": "^18.2.0",
+ "recoil": "^0.7.7"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.23.3",
+ "@babel/plugin-transform-runtime": "^7.23.3",
+ "@babel/preset-env": "^7.23.3",
+ "@babel/preset-react": "^7.23.3",
+ "@babel/preset-typescript": "^7.23.3",
+ "@babel/runtime-corejs3": "^7.23.2",
+ "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
+ "@types/react": "^18.2.37",
+ "@types/react-dom": "^18.2.15",
+ "babel-loader": "^9.1.3",
+ "babel-plugin-istanbul": "^6.1.1",
+ "bn.js": "^5.2.1",
+ "copy-webpack-plugin": "^11.0.0",
+ "core-js": "^3.33.2",
+ "crypto-browserify": "^3.12.0",
+ "css-loader": "^6.8.1",
+ "dotenv": "^16.4.3",
+ "html-webpack-plugin": "^5.5.3",
+ "process": "^0.11.10",
+ "react-refresh": "^0.14.0",
+ "stream-browserify": "^3.0.0",
+ "style-loader": "^3.3.3",
+ "terser-webpack-plugin": "^5.3.9",
+ "webpack": "^5.89.0",
+ "webpack-bundle-analyzer": "^4.10.0",
+ "webpack-cli": "^5.1.4",
+ "webpack-dev-server": "^4.15.1"
+ },
+ "browserslist": [
+ "last 1 Chrome version",
+ "last 1 iOS version",
+ "last 1 Safari version",
+ "last 1 Firefox version",
+ "last 1 Edge version",
+ "last 1 Opera version"
+ ],
+ "depcheck": {
+ "ignorePatterns": [
+ "dist"
+ ],
+ "ignoreMatches": [
+ "process"
+ ]
+ }
+}
diff --git a/ultrasound/ui/public/burn-snx.svg b/ultrasound/ui/public/burn-snx.svg
new file mode 100644
index 000000000..381e4e889
--- /dev/null
+++ b/ultrasound/ui/public/burn-snx.svg
@@ -0,0 +1,9 @@
+
diff --git a/ultrasound/ui/public/burn.svg b/ultrasound/ui/public/burn.svg
new file mode 100644
index 000000000..f97ca035b
--- /dev/null
+++ b/ultrasound/ui/public/burn.svg
@@ -0,0 +1,4 @@
+
diff --git a/ultrasound/ui/public/favicon.ico b/ultrasound/ui/public/favicon.ico
new file mode 100644
index 000000000..5845395c4
Binary files /dev/null and b/ultrasound/ui/public/favicon.ico differ
diff --git a/ultrasound/ui/public/kain.svg b/ultrasound/ui/public/kain.svg
new file mode 100644
index 000000000..e15a9e6c4
--- /dev/null
+++ b/ultrasound/ui/public/kain.svg
@@ -0,0 +1,9 @@
+
diff --git a/ultrasound/ui/public/minted.svg b/ultrasound/ui/public/minted.svg
new file mode 100644
index 000000000..e98af90d7
--- /dev/null
+++ b/ultrasound/ui/public/minted.svg
@@ -0,0 +1,3 @@
+
diff --git a/ultrasound/ui/public/snx-input.svg b/ultrasound/ui/public/snx-input.svg
new file mode 100644
index 000000000..a0b7e4f7f
--- /dev/null
+++ b/ultrasound/ui/public/snx-input.svg
@@ -0,0 +1,4 @@
+
diff --git a/ultrasound/ui/public/snx-small.svg b/ultrasound/ui/public/snx-small.svg
new file mode 100644
index 000000000..0b01cb4e3
--- /dev/null
+++ b/ultrasound/ui/public/snx-small.svg
@@ -0,0 +1,4 @@
+
diff --git a/ultrasound/ui/public/snx.svg b/ultrasound/ui/public/snx.svg
new file mode 100644
index 000000000..f15fa74b5
--- /dev/null
+++ b/ultrasound/ui/public/snx.svg
@@ -0,0 +1,19 @@
+
diff --git a/ultrasound/ui/public/usdc.svg b/ultrasound/ui/public/usdc.svg
new file mode 100644
index 000000000..f3804dd20
--- /dev/null
+++ b/ultrasound/ui/public/usdc.svg
@@ -0,0 +1,12 @@
+
diff --git a/ultrasound/ui/src/App.tsx b/ultrasound/ui/src/App.tsx
new file mode 100644
index 000000000..8730f8cc3
--- /dev/null
+++ b/ultrasound/ui/src/App.tsx
@@ -0,0 +1,12 @@
+import { Flex } from '@chakra-ui/react';
+import { Header } from '../components/Header';
+import { Main } from '../components/Main';
+
+export function App() {
+ return (
+
+
+
+
+ );
+}
diff --git a/ultrasound/ui/src/index.tsx b/ultrasound/ui/src/index.tsx
new file mode 100644
index 000000000..6592cf5cf
--- /dev/null
+++ b/ultrasound/ui/src/index.tsx
@@ -0,0 +1,30 @@
+import { createRoot } from 'react-dom/client';
+import { ChakraProvider } from '@chakra-ui/react';
+import { theme, Fonts } from '@synthetixio/v3-theme';
+import { RecoilRoot } from 'recoil';
+import { Web3OnboardProvider } from '@web3-onboard/react';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { onboard } from '../utils/onboard';
+import { App } from './App';
+import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
+
+if (typeof document !== 'undefined') {
+ // eslint-disable-next-line no-undef
+ const element = document.querySelector('#app');
+ if (element) {
+ const root = createRoot(element);
+ root.render(
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/ultrasound/ui/tsconfig.json b/ultrasound/ui/tsconfig.json
new file mode 100644
index 000000000..f4551678e
--- /dev/null
+++ b/ultrasound/ui/tsconfig.json
@@ -0,0 +1,6 @@
+{
+ "compilerOptions": {
+ "jsx": "react-jsx"
+ },
+ "extends": "../../tsconfig.json"
+}
diff --git a/ultrasound/ui/utils/addresses.ts b/ultrasound/ui/utils/addresses.ts
new file mode 100644
index 000000000..ae8647c55
--- /dev/null
+++ b/ultrasound/ui/utils/addresses.ts
@@ -0,0 +1,7 @@
+export function shortAddress(address?: string) {
+ if (!address) return 'not found';
+ return address
+ .substring(0, 4)
+ .concat('...')
+ .concat(address.substring(address.length - 4));
+}
diff --git a/ultrasound/ui/utils/onboard.ts b/ultrasound/ui/utils/onboard.ts
new file mode 100644
index 000000000..1e29c7f4d
--- /dev/null
+++ b/ultrasound/ui/utils/onboard.ts
@@ -0,0 +1,54 @@
+import { NETWORKS, appMetadata } from '@snx-v3/useBlockchain';
+import injectedModule, { ProviderLabel } from '@web3-onboard/injected-wallets';
+import trezorModule from '@web3-onboard/trezor';
+import ledgerModule from '@web3-onboard/ledger';
+import walletConnectModule from '@web3-onboard/walletconnect';
+import coinbaseModule from '@web3-onboard/coinbase';
+import { init } from '@web3-onboard/react';
+
+const supportedNetworks = [8453];
+
+// Filter networks to only supported ones
+export const networks = NETWORKS.filter((n) => supportedNetworks.includes(n.id)).map((n) => ({
+ id: n.id,
+ token: n.token,
+ label: n.label,
+ rpcUrl: n.rpcUrl(),
+}));
+
+export const onboard = init({
+ wallets: [
+ injectedModule({ displayUnavailable: [ProviderLabel.MetaMask, ProviderLabel.Trust] }),
+ trezorModule({
+ appUrl: 'https://liquidity.synthetix.eth.limo',
+ email: 'info@synthetix.io',
+ }),
+ ledgerModule({
+ projectId: 'd6eac005846a1c3be1f8eea3a294eed9',
+ walletConnectVersion: 2,
+ }),
+ walletConnectModule({
+ version: 2,
+ projectId: 'd6eac005846a1c3be1f8eea3a294eed9',
+ dappUrl: 'liquidity.synthetix.eth.limo',
+ }),
+ // gnosisModule(),
+ coinbaseModule(),
+ ],
+ chains: [...networks],
+ appMetadata: {
+ ...appMetadata,
+ name: 'Synthetix Governance',
+ },
+ accountCenter: {
+ desktop: {
+ enabled: false,
+ },
+ mobile: {
+ enabled: false,
+ },
+ },
+ notify: {
+ enabled: false,
+ },
+});
diff --git a/ultrasound/ui/webpack.config.js b/ultrasound/ui/webpack.config.js
new file mode 100644
index 000000000..11a0ec7c4
--- /dev/null
+++ b/ultrasound/ui/webpack.config.js
@@ -0,0 +1,188 @@
+const path = require('path');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const TerserPlugin = require('terser-webpack-plugin');
+const webpack = require('webpack');
+const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
+const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
+const CopyWebpackPlugin = require('copy-webpack-plugin');
+require('dotenv').config();
+
+// For depcheck to be happy
+require.resolve('webpack-dev-server');
+
+const isProd = process.env.NODE_ENV === 'production';
+const isTest = process.env.NODE_ENV === 'test';
+
+const htmlPlugin = new HtmlWebpackPlugin({
+ template: './index.html',
+ scriptLoading: 'defer',
+ minify: false,
+ hash: false,
+ xhtml: true,
+ excludeChunks: ['main'],
+});
+
+const babelRule = {
+ test: /\.(ts|tsx|js|jsx)$/,
+ include: [
+ // Need to list all the folders in v3 and outside (if used)
+ /ultrasound\/ui/,
+ /theme/,
+ /contracts/,
+ /liquidity\/components/,
+ /liquidity\/lib/,
+ ],
+ resolve: {
+ fullySpecified: false,
+ },
+ use: {
+ loader: require.resolve('babel-loader'),
+ options: {
+ configFile: path.resolve(__dirname, 'babel.config.js'),
+ },
+ },
+};
+
+const imgRule = {
+ test: /\.(png|jpg|ico|gif|woff|woff2|ttf|eot|doc|pdf|zip|wav|avi|txt|webp|svg)$/,
+ type: 'asset',
+ parser: {
+ dataUrlCondition: {
+ maxSize: 4 * 1024, // 4kb
+ },
+ },
+};
+
+const cssRule = {
+ test: /\.css$/,
+ include: [new RegExp('./src'), new RegExp('reactflow')],
+ exclude: [],
+ use: [
+ {
+ loader: require.resolve('style-loader'),
+ },
+ {
+ loader: require.resolve('css-loader'),
+ },
+ ],
+};
+
+const devServer = {
+ port: '3000',
+
+ hot: !isTest,
+ liveReload: false,
+
+ historyApiFallback: true,
+
+ devMiddleware: {
+ writeToDisk: !isTest,
+ publicPath: '',
+ },
+
+ client: {
+ logging: 'log',
+ overlay: false,
+ progress: false,
+ },
+
+ static: './public',
+
+ headers: { 'Access-Control-Allow-Origin': '*' },
+ allowedHosts: 'all',
+ open: false,
+ compress: false,
+};
+
+module.exports = {
+ devtool: isProd ? 'source-map' : isTest ? false : 'eval',
+ devServer,
+ mode: isProd ? 'production' : 'development',
+ entry: './src/index.tsx',
+
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ publicPath: '',
+ filename: '[name].js',
+ chunkFilename: isProd ? 'chunk/[name].[contenthash:8].js' : '[name].js',
+ assetModuleFilename: '[name].[contenthash:8][ext]',
+ clean: true,
+ },
+
+ optimization: {
+ runtimeChunk: false,
+ splitChunks: {
+ chunks: 'async',
+ maxAsyncRequests: 10,
+ maxInitialRequests: 10,
+ hidePathInfo: true,
+ automaticNameDelimiter: '--',
+ name: false,
+ },
+ moduleIds: isProd ? 'deterministic' : 'named',
+ chunkIds: isProd ? 'deterministic' : 'named',
+ minimize: isProd,
+ minimizer: [new TerserPlugin()],
+ innerGraph: true,
+ emitOnErrors: false,
+ },
+
+ plugins: [htmlPlugin]
+ .concat(isProd ? [new CopyWebpackPlugin({ patterns: [{ from: 'public', to: '' }] })] : [])
+
+ .concat([new webpack.NormalModuleReplacementPlugin(/^bn.js$/, require.resolve('bn.js'))])
+
+ .concat([
+ new webpack.NormalModuleReplacementPlugin(
+ new RegExp(`^@synthetixio/v3-theme$`),
+ path.resolve(path.dirname(require.resolve(`@synthetixio/v3-theme/package.json`)), 'src')
+ ),
+ ])
+ .concat([])
+
+ .concat([
+ new webpack.ProvidePlugin({
+ Buffer: ['buffer', 'Buffer'],
+ process: 'process/browser.js',
+ }),
+ ])
+
+ .concat(isProd ? [] : isTest ? [] : [new ReactRefreshWebpackPlugin({ overlay: false })])
+ .concat(
+ process.env.GENERATE_BUNDLE_REPORT === 'true'
+ ? [
+ new BundleAnalyzerPlugin({
+ analyzerMode: 'static',
+ reportFilename: path.resolve(__dirname, 'tmp', 'webpack.html'),
+ openAnalyzer: false,
+ generateStatsFile: false,
+ }),
+ ]
+ : []
+ )
+ .concat(
+ new webpack.DefinePlugin({
+ 'process.env.INFURA_KEY': JSON.stringify(process.env.INFURA_KEY),
+ 'process.env.WC_PROJECT_ID': JSON.stringify(process.env.WC_PROJECT_ID),
+ 'process.env.PYTH_MAINNET_ENDPOINT': JSON.stringify(process.env.PYTH_MAINNET_ENDPOINT),
+ 'process.env.PYTH_TESTNET_ENDPOINT': JSON.stringify(process.env.PYTH_TESTNET_ENDPOINT),
+ })
+ ),
+
+ resolve: {
+ fallback: {
+ buffer: require.resolve('buffer'),
+ stream: require.resolve('stream-browserify'),
+ crypto: require.resolve('crypto-browserify'),
+ process: require.resolve('process/browser.js'),
+ http: false,
+ https: false,
+ os: false,
+ },
+ extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs'],
+ },
+
+ module: {
+ rules: [babelRule, imgRule, cssRule],
+ },
+};
diff --git a/yarn.lock b/yarn.lock
index cdd2b1e77..2032bf469 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4149,6 +4149,13 @@ __metadata:
languageName: node
linkType: hard
+"@kurkle/color@npm:^0.3.0":
+ version: 0.3.2
+ resolution: "@kurkle/color@npm:0.3.2"
+ checksum: 079c4b7688061070f1d570cee4cf0e7c4085867b940688ff80356a56825a5ace7077257c9ec5863eb344a8eae78379388ab57cdf9d75c491389fad56f411cd43
+ languageName: node
+ linkType: hard
+
"@ledgerhq/connect-kit-loader@npm:^1.1.0":
version: 1.1.8
resolution: "@ledgerhq/connect-kit-loader@npm:1.1.8"
@@ -6605,6 +6612,74 @@ __metadata:
languageName: unknown
linkType: soft
+"@snx-v3/ultrasound-homes@workspace:ultrasound/ui":
+ version: 0.0.0-use.local
+ resolution: "@snx-v3/ultrasound-homes@workspace:ultrasound/ui"
+ dependencies:
+ "@babel/core": "npm:^7.23.3"
+ "@babel/plugin-transform-runtime": "npm:^7.23.3"
+ "@babel/preset-env": "npm:^7.23.3"
+ "@babel/preset-react": "npm:^7.23.3"
+ "@babel/preset-typescript": "npm:^7.23.3"
+ "@babel/runtime-corejs3": "npm:^7.23.2"
+ "@chakra-ui/icons": "npm:^2.1.1"
+ "@chakra-ui/react": "npm:^2.8.2"
+ "@emotion/react": "npm:^11.11.1"
+ "@emotion/styled": "npm:^11.11.0"
+ "@pmmmwh/react-refresh-webpack-plugin": "npm:^0.5.11"
+ "@snx-v3/isBaseAndromeda": "workspace:*"
+ "@snx-v3/tsHelpers": "workspace:*"
+ "@snx-v3/useApprove": "workspace:*"
+ "@snx-v3/useBlockchain": "workspace:*"
+ "@snx-v3/useCoreProxy": "workspace:*"
+ "@snx-v3/useGasOptions": "workspace:*"
+ "@snx-v3/useGasPrice": "workspace:*"
+ "@snx-v3/useGasSpeed": "workspace:*"
+ "@snx-v3/useSpotMarketProxy": "workspace:*"
+ "@snx-v3/useTokenBalance": "workspace:*"
+ "@snx-v3/useUSDProxy": "workspace:*"
+ "@snx-v3/withERC7412": "workspace:*"
+ "@synthetixio/v3-contracts": "workspace:*"
+ "@synthetixio/v3-theme": "workspace:*"
+ "@synthetixio/wei": "npm:^2.74.4"
+ "@tanstack/react-query": "npm:^5.8.3"
+ "@tanstack/react-query-devtools": "npm:^5.8.3"
+ "@types/react": "npm:^18.2.37"
+ "@types/react-dom": "npm:^18.2.15"
+ "@web3-onboard/coinbase": "npm:^2.2.6"
+ "@web3-onboard/injected-wallets": "npm:^2.10.11"
+ "@web3-onboard/ledger": "npm:^2.5.2"
+ "@web3-onboard/react": "npm:^2.8.13"
+ "@web3-onboard/trezor": "npm:^2.4.3"
+ "@web3-onboard/walletconnect": "npm:^2.5.3"
+ babel-loader: "npm:^9.1.3"
+ babel-plugin-istanbul: "npm:^6.1.1"
+ bn.js: "npm:^5.2.1"
+ chart.js: "npm:^4.0.0"
+ copy-webpack-plugin: "npm:^11.0.0"
+ core-js: "npm:^3.33.2"
+ crypto-browserify: "npm:^3.12.0"
+ css-loader: "npm:^6.8.1"
+ dotenv: "npm:^16.4.3"
+ ethers: "npm:^5.7.2"
+ framer-motion: "npm:^10.16.5"
+ html-webpack-plugin: "npm:^5.5.3"
+ process: "npm:^0.11.10"
+ react: "npm:^18.2.0"
+ react-chartjs-2: "npm:^5.2.0"
+ react-dom: "npm:^18.2.0"
+ react-refresh: "npm:^0.14.0"
+ recoil: "npm:^0.7.7"
+ stream-browserify: "npm:^3.0.0"
+ style-loader: "npm:^3.3.3"
+ terser-webpack-plugin: "npm:^5.3.9"
+ webpack: "npm:^5.89.0"
+ webpack-bundle-analyzer: "npm:^4.10.0"
+ webpack-cli: "npm:^5.1.4"
+ webpack-dev-server: "npm:^4.15.1"
+ languageName: unknown
+ linkType: soft
+
"@snx-v3/useAccountCollateral@workspace:*, @snx-v3/useAccountCollateral@workspace:liquidity/lib/useAccountCollateral":
version: 0.0.0-use.local
resolution: "@snx-v3/useAccountCollateral@workspace:liquidity/lib/useAccountCollateral"
@@ -7138,7 +7213,7 @@ __metadata:
languageName: unknown
linkType: soft
-"@snx-v3/useSpotMarketProxy@workspace:liquidity/lib/useSpotMarketProxy":
+"@snx-v3/useSpotMarketProxy@workspace:*, @snx-v3/useSpotMarketProxy@workspace:liquidity/lib/useSpotMarketProxy":
version: 0.0.0-use.local
resolution: "@snx-v3/useSpotMarketProxy@workspace:liquidity/lib/useSpotMarketProxy"
dependencies:
@@ -12203,6 +12278,15 @@ __metadata:
languageName: node
linkType: hard
+"chart.js@npm:^4.0.0":
+ version: 4.4.2
+ resolution: "chart.js@npm:4.4.2"
+ dependencies:
+ "@kurkle/color": "npm:^0.3.0"
+ checksum: 609444dfc9e847e4c891884309d6083464333e39a7266996fa15f622a44d0c5202c20c86b3bfb1d72b3769096f71c80e131860270c39ce1291cac52b9f45dc6d
+ languageName: node
+ linkType: hard
+
"check-more-types@npm:^2.24.0":
version: 2.24.0
resolution: "check-more-types@npm:2.24.0"
@@ -22801,6 +22885,16 @@ __metadata:
languageName: node
linkType: hard
+"react-chartjs-2@npm:^5.2.0":
+ version: 5.2.0
+ resolution: "react-chartjs-2@npm:5.2.0"
+ peerDependencies:
+ chart.js: ^4.1.1
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ checksum: 0a70b60e1a0d1f0cecdd69d70d9ac1c191618c1033d8f942d4728f5eb84c893c82c9e3318be8a3158eb3a7186c0251a8fe0356c9610ea0f0f3f2d4a9c4a0b388
+ languageName: node
+ linkType: hard
+
"react-clientside-effect@npm:^1.2.6":
version: 1.2.6
resolution: "react-clientside-effect@npm:1.2.6"