From 65cef2a9dfaeeb4fd6cb3980dc83d77e77661500 Mon Sep 17 00:00:00 2001
From: memosys <82053242+memosys@users.noreply.github.com>
Date: Tue, 16 Apr 2024 16:02:58 +0530
Subject: [PATCH 1/8] basic inputs
---
.../components/dashboard/insights.tsx | 2 +-
.../frontend/components/swap/Redeem.tsx | 7 ++
web-portal/frontend/components/swap/Swap.tsx | 89 +++++++++++++++++++
web-portal/frontend/pages/swap/index.tsx | 50 +++++++++++
web-portal/frontend/styles/tabs.module.css | 5 ++
5 files changed, 152 insertions(+), 1 deletion(-)
create mode 100644 web-portal/frontend/components/swap/Redeem.tsx
create mode 100644 web-portal/frontend/components/swap/Swap.tsx
create mode 100644 web-portal/frontend/pages/swap/index.tsx
diff --git a/web-portal/frontend/components/dashboard/insights.tsx b/web-portal/frontend/components/dashboard/insights.tsx
index ef706ec3..c1a0ae86 100644
--- a/web-portal/frontend/components/dashboard/insights.tsx
+++ b/web-portal/frontend/components/dashboard/insights.tsx
@@ -6,7 +6,7 @@ import {
Card,
RingProgress,
} from "@mantine/core";
-import React, { useEffect, useState } from "react";
+import React from "react";
import classes from "@frontend/styles/insight.module.css";
import { AreaChart } from "@mantine/charts";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
diff --git a/web-portal/frontend/components/swap/Redeem.tsx b/web-portal/frontend/components/swap/Redeem.tsx
new file mode 100644
index 00000000..58fe9672
--- /dev/null
+++ b/web-portal/frontend/components/swap/Redeem.tsx
@@ -0,0 +1,7 @@
+export default function Redeem() {
+ return (
+
+
Redeem
+
+ );
+}
diff --git a/web-portal/frontend/components/swap/Swap.tsx b/web-portal/frontend/components/swap/Swap.tsx
new file mode 100644
index 00000000..6e8e0e70
--- /dev/null
+++ b/web-portal/frontend/components/swap/Swap.tsx
@@ -0,0 +1,89 @@
+import { Stack, Button, Flex, Select, TextInput } from "@mantine/core";
+import { karla } from "@frontend/utils/theme";
+
+export default function Swap() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/web-portal/frontend/pages/swap/index.tsx b/web-portal/frontend/pages/swap/index.tsx
new file mode 100644
index 00000000..b42eb681
--- /dev/null
+++ b/web-portal/frontend/pages/swap/index.tsx
@@ -0,0 +1,50 @@
+import DashboardLayout from "@frontend/components/dashboard/layout";
+import { Stack, Tabs, rem } from "@mantine/core";
+import { useState } from "react";
+import { crimson } from "@frontend/utils/theme";
+import Swap from "@frontend/components/swap/Swap";
+import Redeem from "@frontend/components/swap/Redeem";
+import classes from "@frontend/styles/tabs.module.css";
+
+export default function SwapOrRedeem() {
+ const [value, setValue] = useState("swap");
+
+ return (
+
+
+ setValue(value === "swap" ? "redeem" : "swap")}
+ >
+
+ Swap
+ Redeem
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/web-portal/frontend/styles/tabs.module.css b/web-portal/frontend/styles/tabs.module.css
index 2c864e9c..d043c96d 100644
--- a/web-portal/frontend/styles/tabs.module.css
+++ b/web-portal/frontend/styles/tabs.module.css
@@ -6,3 +6,8 @@
.tab:hover {
background-color: transparent;
}
+
+.tabLabel {
+ font-size: 1.1rem;
+ font-weight: 700;
+}
From ee8b63727f6021bd3e820b353b165bf662468dde Mon Sep 17 00:00:00 2001
From: memosys <82053242+memosys@users.noreply.github.com>
Date: Tue, 16 Apr 2024 18:31:14 +0530
Subject: [PATCH 2/8] save:progress
save
---
.../components/swap/SearchableSelectModal.tsx | 74 +++++++++
web-portal/frontend/components/swap/Swap.tsx | 142 +++++++++++-------
web-portal/frontend/next.config.js | 4 +
web-portal/frontend/pages/swap/index.tsx | 70 ++++++++-
web-portal/frontend/utils/Web3Provider.tsx | 28 +++-
web-portal/frontend/utils/atoms.ts | 3 +-
web-portal/frontend/utils/hooks.ts | 1 +
web-portal/frontend/utils/siwe.ts | 1 -
web-portal/frontend/utils/tokens.ts | 9 ++
web-portal/frontend/utils/types.ts | 10 ++
10 files changed, 275 insertions(+), 67 deletions(-)
create mode 100644 web-portal/frontend/components/swap/SearchableSelectModal.tsx
create mode 100644 web-portal/frontend/utils/tokens.ts
diff --git a/web-portal/frontend/components/swap/SearchableSelectModal.tsx b/web-portal/frontend/components/swap/SearchableSelectModal.tsx
new file mode 100644
index 00000000..86106c06
--- /dev/null
+++ b/web-portal/frontend/components/swap/SearchableSelectModal.tsx
@@ -0,0 +1,74 @@
+import React, { useState } from "react";
+import { Modal, TextInput, Text, Flex, Stack } from "@mantine/core";
+import _ from "lodash";
+import Image from "next/image";
+import { IToken } from "@frontend/utils/types";
+
+interface Props {
+ options: IToken[];
+ opened: boolean;
+ onSelect: (option: IToken) => void;
+ onClose: () => void;
+}
+
+export const SearchableSelectModal: React.FC = ({
+ options,
+ onSelect,
+ opened,
+ onClose,
+}) => {
+ const [searchValue, setSearchValue] = useState(null);
+
+ const onChange = (newValue: string) => {
+ setSearchValue(newValue);
+ };
+
+ const filteredOptions = _.filter(options, (option: IToken) =>
+ typeof searchValue === "string"
+ ? _.toLower(option.symbol).includes(_.toLower(searchValue))
+ : options,
+ );
+
+ return (
+
+ onChange(event.target.value)}
+ title="Select Token to swap"
+ />
+
+ {filteredOptions.map((option) => (
+ onSelect(option)}
+ align="center"
+ p={8}
+ >
+
+
+
+ {_.get(option, "symbol") + ` - ` + _.get(option, "name")}
+
+ {_.get(option, "address")}
+
+
+ ))}
+
+
+ );
+};
diff --git a/web-portal/frontend/components/swap/Swap.tsx b/web-portal/frontend/components/swap/Swap.tsx
index 6e8e0e70..2f903f10 100644
--- a/web-portal/frontend/components/swap/Swap.tsx
+++ b/web-portal/frontend/components/swap/Swap.tsx
@@ -1,88 +1,122 @@
-import { Stack, Button, Flex, Select, TextInput } from "@mantine/core";
+import { useState } from "react";
+import { Flex, Stack, Button, TextInput } from "@mantine/core";
+import { useAtomValue } from "jotai";
+import _ from "lodash";
+import Image from "next/image";
import { karla } from "@frontend/utils/theme";
+import { tokenDataAtom } from "@frontend/utils/atoms";
+import { IToken } from "@frontend/utils/types";
+import { SearchableSelectModal } from "./SearchableSelectModal";
+
+// Common styles for TextInput and Select components
+const commonStyles = {
+ input: {
+ outline: "none",
+ border: "none",
+ background: "none",
+ fontSize: 24,
+ },
+ label: {
+ color: "#000",
+ marginLeft: 10,
+ },
+};
+
+export default function Swap({ defaultToken }: { defaultToken: IToken }) {
+ const tokenData = useAtomValue(tokenDataAtom);
+ const [swapValue, setSwapValue] = useState(0);
+
+ const [selectedTokenData, setSelectedTokenData] =
+ useState(defaultToken);
+
+ const handleTokenChange = (token: IToken) => {
+ setSelectedTokenData(token);
+ };
+
+ const [opened, setOpened] = useState(false);
-export default function Swap() {
return (
+ setOpened(false)}
+ opened={opened}
+ options={tokenData}
+ onSelect={(token: IToken) => {
+ handleTokenChange(token);
+ setOpened(false);
+ }}
+ />
setSwapValue(parseFloat(e.target.value))}
styles={{
- input: {
- outline: "none",
- border: "none",
- fill: "#fff",
- fontSize: 24,
- },
- label: {
- color: "#000",
- fontFamily: karla.style.fontFamily,
- marginLeft: 10,
- },
- }}
- wrapperProps={{
- style: {
- width: "100%",
- },
- }}
- />
-
+
+
-
+ bg={"blue"}
+ >
+
+ PORTR
+
+
);
diff --git a/web-portal/frontend/next.config.js b/web-portal/frontend/next.config.js
index e953c480..1cfc9ccc 100644
--- a/web-portal/frontend/next.config.js
+++ b/web-portal/frontend/next.config.js
@@ -12,6 +12,10 @@ const nextConfig = {
hostname: "api.web3modal.com",
port: "",
},
+ {
+ protocol: "https",
+ hostname: "ethereum-optimism.github.io",
+ },
],
},
async rewrites() {
diff --git a/web-portal/frontend/pages/swap/index.tsx b/web-portal/frontend/pages/swap/index.tsx
index b42eb681..b2861bb0 100644
--- a/web-portal/frontend/pages/swap/index.tsx
+++ b/web-portal/frontend/pages/swap/index.tsx
@@ -1,13 +1,36 @@
import DashboardLayout from "@frontend/components/dashboard/layout";
import { Stack, Tabs, rem } from "@mantine/core";
-import { useState } from "react";
+import { useEffect, useState } from "react";
import { crimson } from "@frontend/utils/theme";
import Swap from "@frontend/components/swap/Swap";
import Redeem from "@frontend/components/swap/Redeem";
import classes from "@frontend/styles/tabs.module.css";
+import { IToken } from "@frontend/utils/types";
+import { useSetAtom } from "jotai";
+import { tokenDataAtom } from "@frontend/utils/atoms";
+import { useChainId } from "wagmi";
+import _ from "lodash";
-export default function SwapOrRedeem() {
+export default function SwapOrRedeem({
+ data,
+ defaultToken,
+}: {
+ data: IToken[];
+ defaultToken: IToken;
+}) {
const [value, setValue] = useState("swap");
+ const chainId = useChainId();
+
+ const setTokenData = useSetAtom(tokenDataAtom);
+
+ useEffect(() => {
+ setTokenData(
+ _.uniqBy(
+ _.filter(data, (token) => token.chainId === chainId),
+ "address",
+ ),
+ );
+ });
return (
@@ -15,7 +38,6 @@ export default function SwapOrRedeem() {
style={{
alignItems: "center",
justifyContent: "center",
- fontFamily: crimson.style.fontFamily,
fontWeight: 700,
fontSize: rem(20),
}}
@@ -33,12 +55,32 @@ export default function SwapOrRedeem() {
onChange={() => setValue(value === "swap" ? "redeem" : "swap")}
>
- Swap
- Redeem
+
+ Swap
+
+
+ Redeem
+
-
-
+
+
@@ -48,3 +90,17 @@ export default function SwapOrRedeem() {
);
}
+
+export async function getServerSideProps() {
+ const res = await fetch("https://static.optimism.io/optimism.tokenlist.json");
+ const data = await res.json();
+
+ const { tokens } = data;
+ const defaultToken = _.filter(tokens, { name: "Ether" })[0];
+ return {
+ props: {
+ data: tokens satisfies IToken[],
+ defaultToken: defaultToken satisfies IToken,
+ },
+ };
+}
diff --git a/web-portal/frontend/utils/Web3Provider.tsx b/web-portal/frontend/utils/Web3Provider.tsx
index 1e8c3800..fcced26f 100644
--- a/web-portal/frontend/utils/Web3Provider.tsx
+++ b/web-portal/frontend/utils/Web3Provider.tsx
@@ -1,12 +1,21 @@
import { defaultWagmiConfig } from "@web3modal/wagmi/react/config";
import { cookieStorage, createStorage } from "wagmi";
-import { mainnet, sepolia } from "wagmi/chains";
+import {
+ arbitrum,
+ base,
+ blast,
+ mainnet,
+ mode,
+ optimism,
+ polygon,
+ sepolia,
+ zkSync,
+ scroll,
+} from "wagmi/chains";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Provider as JotaiProvider, useSetAtom } from "jotai";
import { State, WagmiProvider } from "wagmi";
import { ReactNode } from "react";
-import { useSession } from "./hooks";
-import { sessionAtom } from "./atoms";
export const projectId = String(
process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID,
@@ -24,7 +33,18 @@ const metadata = {
};
// Create wagmiConfig
-const chains = [mainnet, sepolia] as const;
+const chains = [
+ mainnet,
+ sepolia,
+ optimism,
+ base,
+ mode,
+ blast,
+ arbitrum,
+ polygon,
+ zkSync,
+ scroll,
+] as const;
export const config = defaultWagmiConfig({
chains,
projectId,
diff --git a/web-portal/frontend/utils/atoms.ts b/web-portal/frontend/utils/atoms.ts
index d7e18d0f..9a703574 100644
--- a/web-portal/frontend/utils/atoms.ts
+++ b/web-portal/frontend/utils/atoms.ts
@@ -1,8 +1,9 @@
import { atom } from "jotai";
-import { IEndpoint, ISession, IRuleType } from "./types";
+import { IEndpoint, ISession, IRuleType, IToken } from "./types";
export const sessionAtom = atom({});
export const appsAtom = atom([]);
export const endpointsAtom = atom([]);
export const ruleTypesAtom = atom([]);
export const existingRuleValuesAtom = atom([]);
export const billingHistoryAtom = atom([]);
+export const tokenDataAtom = atom([]);
diff --git a/web-portal/frontend/utils/hooks.ts b/web-portal/frontend/utils/hooks.ts
index 0b5ad342..91c3179e 100644
--- a/web-portal/frontend/utils/hooks.ts
+++ b/web-portal/frontend/utils/hooks.ts
@@ -2,6 +2,7 @@ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { getSession } from "./siwe";
import { useAccount } from "wagmi";
import { usePathname, useRouter } from "next/navigation";
+import { IToken } from "./types";
export const useSession = () => {
const { address, isConnected } = useAccount();
diff --git a/web-portal/frontend/utils/siwe.ts b/web-portal/frontend/utils/siwe.ts
index a08848d2..230112b9 100644
--- a/web-portal/frontend/utils/siwe.ts
+++ b/web-portal/frontend/utils/siwe.ts
@@ -1,7 +1,6 @@
import { SiweMessage } from "siwe";
import Cookies from "js-cookie";
import { createSIWEConfig } from "@web3modal/siwe";
-import { revalidatePath } from "next/cache";
export const getNonce = async () => {
const res = await fetch("/api/siwe", { method: "PUT" });
diff --git a/web-portal/frontend/utils/tokens.ts b/web-portal/frontend/utils/tokens.ts
new file mode 100644
index 00000000..c236f9be
--- /dev/null
+++ b/web-portal/frontend/utils/tokens.ts
@@ -0,0 +1,9 @@
+export const popularTokens = [
+ "ETH",
+ "WETH",
+ "WBTC",
+ "USDC",
+ "USDT",
+ "DAI",
+ "MATIC",
+];
diff --git a/web-portal/frontend/utils/types.ts b/web-portal/frontend/utils/types.ts
index a709ce09..d0f5dceb 100644
--- a/web-portal/frontend/utils/types.ts
+++ b/web-portal/frontend/utils/types.ts
@@ -76,3 +76,13 @@ export interface IBill {
createdAt?: string;
transactionType?: string;
}
+
+export interface IToken {
+ chainId: number;
+ address: string;
+ name: string;
+ symbol: string;
+ decimals: number;
+ logoURI: string;
+ extensions?: any;
+}
From d174f7d55e528f6ae2d2e9c854bb4cc78fe3350c Mon Sep 17 00:00:00 2001
From: memosys <82053242+memosys@users.noreply.github.com>
Date: Sun, 21 Apr 2024 13:22:55 +0530
Subject: [PATCH 3/8] save
---
web-portal/frontend/components/swap/Swap.tsx | 66 +++++++++++++++-----
web-portal/frontend/utils/types.ts | 2 +-
2 files changed, 51 insertions(+), 17 deletions(-)
diff --git a/web-portal/frontend/components/swap/Swap.tsx b/web-portal/frontend/components/swap/Swap.tsx
index 2f903f10..ac34be83 100644
--- a/web-portal/frontend/components/swap/Swap.tsx
+++ b/web-portal/frontend/components/swap/Swap.tsx
@@ -1,5 +1,5 @@
import { useState } from "react";
-import { Flex, Stack, Button, TextInput } from "@mantine/core";
+import { Flex, Stack, Button, TextInput, Text } from "@mantine/core";
import { useAtomValue } from "jotai";
import _ from "lodash";
import Image from "next/image";
@@ -7,6 +7,10 @@ import { karla } from "@frontend/utils/theme";
import { tokenDataAtom } from "@frontend/utils/atoms";
import { IToken } from "@frontend/utils/types";
import { SearchableSelectModal } from "./SearchableSelectModal";
+import { useAccount, useBalance, useChainId } from "wagmi";
+import { zeroAddress } from "viem";
+
+import Link from "next/link";
// Common styles for TextInput and Select components
const commonStyles = {
@@ -26,6 +30,7 @@ export default function Swap({ defaultToken }: { defaultToken: IToken }) {
const tokenData = useAtomValue(tokenDataAtom);
const [swapValue, setSwapValue] = useState(0);
+ const chainId = useChainId();
const [selectedTokenData, setSelectedTokenData] =
useState(defaultToken);
@@ -35,6 +40,17 @@ export default function Swap({ defaultToken }: { defaultToken: IToken }) {
const [opened, setOpened] = useState(false);
+ const { address } = useAccount();
+
+ const { data: tokenBalance } = useBalance({
+ token:
+ selectedTokenData?.address != zeroAddress
+ ? selectedTokenData?.address
+ : undefined,
+ address,
+ chainId,
+ });
+
return (
-
+
+
+
+
+
+ ${(Number(swapValue || 0) * 0.01).toFixed(6)}
+
+
+ {Number(_.get(tokenBalance, "formatted")).toFixed(6)}
+
+
+ setSwapValue(Number(_.get(tokenBalance, "formatted")))
+ }
+ >
+ max
+
+
-
Date: Sun, 21 Apr 2024 16:27:11 +0530
Subject: [PATCH 4/8] save:progress
---
web-portal/frontend/components/swap/Swap.tsx | 150 ++++++++++++-------
web-portal/frontend/pages/swap/index.tsx | 9 +-
web-portal/frontend/utils/Web3Provider.tsx | 11 +-
web-portal/frontend/utils/hooks.ts | 29 +++-
4 files changed, 125 insertions(+), 74 deletions(-)
diff --git a/web-portal/frontend/components/swap/Swap.tsx b/web-portal/frontend/components/swap/Swap.tsx
index ac34be83..96912690 100644
--- a/web-portal/frontend/components/swap/Swap.tsx
+++ b/web-portal/frontend/components/swap/Swap.tsx
@@ -1,5 +1,5 @@
import { useState } from "react";
-import { Flex, Stack, Button, TextInput, Text } from "@mantine/core";
+import { Flex, Stack, Button, TextInput, Text, Select } from "@mantine/core";
import { useAtomValue } from "jotai";
import _ from "lodash";
import Image from "next/image";
@@ -10,7 +10,8 @@ import { SearchableSelectModal } from "./SearchableSelectModal";
import { useAccount, useBalance, useChainId } from "wagmi";
import { zeroAddress } from "viem";
-import Link from "next/link";
+import { chains } from "@frontend/utils/Web3Provider";
+import { useQuote } from "@frontend/utils/hooks";
// Common styles for TextInput and Select components
const commonStyles = {
@@ -26,11 +27,15 @@ const commonStyles = {
},
};
+const chainOptions = _.map(chains, "name");
+
export default function Swap({ defaultToken }: { defaultToken: IToken }) {
const tokenData = useAtomValue(tokenDataAtom);
+
const [swapValue, setSwapValue] = useState(0);
const chainId = useChainId();
+ const [selectedChainId, setSelectedChainId] = useState(chainId);
const [selectedTokenData, setSelectedTokenData] =
useState(defaultToken);
@@ -51,79 +56,112 @@ export default function Swap({ defaultToken }: { defaultToken: IToken }) {
chainId,
});
+ const filteredTokenData = _.filter(
+ tokenData,
+ (t) => t.chainId === selectedChainId,
+ );
+
+ const { data: quote } = useQuote({
+ sellToken: selectedTokenData?.address,
+ amount: String(swapValue * 10 ** 4),
+ });
+
+ console.log("quote", quote);
return (
setOpened(false)}
opened={opened}
- options={tokenData}
+ options={filteredTokenData}
onSelect={(token: IToken) => {
handleTokenChange(token);
setOpened(false);
}}
/>
- {
+ const selectedChain = _.find(
+ chains,
+ (c) => _.toLower(c.name) === _.toLower(val as string),
+ );
+ setSelectedChainId(selectedChain?.id as number);
}}
- >
- setSwapValue(parseFloat(e.target.value))}
- styles={{
- ...commonStyles,
- input: { ...commonStyles.input, fill: "#fff" },
+ label="Select Network"
+ />
+
+
+
-
-
-
-
-
- ${(Number(swapValue || 0) * 0.01).toFixed(6)}
-
-
- {Number(_.get(tokenBalance, "formatted")).toFixed(6)}
-
-
- setSwapValue(Number(_.get(tokenBalance, "formatted")))
- }
- >
- max
-
+ >
+ setSwapValue(parseFloat(e.target.value))}
+ styles={{
+ ...commonStyles,
+ input: { ...commonStyles.input, fill: "#fff" },
+ }}
+ />
+
+
+
-
+
+ ${(Number(swapValue || 0) * 0.01).toFixed(6)}
+
+
+ {Number(_.get(tokenBalance, "formatted")).toFixed(6)}
+
+
+ setSwapValue(Number(_.get(tokenBalance, "formatted")))
+ }
+ >
+ max
+
+
+
+
{
- setTokenData(
- _.uniqBy(
- _.filter(data, (token) => token.chainId === chainId),
- "address",
- ),
- );
+ setTokenData(data);
});
return (
diff --git a/web-portal/frontend/utils/Web3Provider.tsx b/web-portal/frontend/utils/Web3Provider.tsx
index fcced26f..31d82f1a 100644
--- a/web-portal/frontend/utils/Web3Provider.tsx
+++ b/web-portal/frontend/utils/Web3Provider.tsx
@@ -3,14 +3,10 @@ import { cookieStorage, createStorage } from "wagmi";
import {
arbitrum,
base,
- blast,
mainnet,
- mode,
optimism,
polygon,
sepolia,
- zkSync,
- scroll,
} from "wagmi/chains";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Provider as JotaiProvider, useSetAtom } from "jotai";
@@ -33,18 +29,15 @@ const metadata = {
};
// Create wagmiConfig
-const chains = [
+export const chains = [
mainnet,
sepolia,
optimism,
base,
- mode,
- blast,
arbitrum,
polygon,
- zkSync,
- scroll,
] as const;
+
export const config = defaultWagmiConfig({
chains,
projectId,
diff --git a/web-portal/frontend/utils/hooks.ts b/web-portal/frontend/utils/hooks.ts
index 91c3179e..47da3ef4 100644
--- a/web-portal/frontend/utils/hooks.ts
+++ b/web-portal/frontend/utils/hooks.ts
@@ -2,7 +2,6 @@ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { getSession } from "./siwe";
import { useAccount } from "wagmi";
import { usePathname, useRouter } from "next/navigation";
-import { IToken } from "./types";
export const useSession = () => {
const { address, isConnected } = useAccount();
@@ -190,3 +189,31 @@ export const useSecretKeyMutation = (appId: string) => {
},
});
};
+
+export const useQuote = ({
+ sellToken,
+ amount,
+}: {
+ sellToken: string;
+ amount: string;
+}) => {
+ const fetchQuote = async () => {
+ const response = await fetch(
+ `https://api.0x.org/swap/v1/price?sellToken=${sellToken}&buyToken=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2&sellAmount=${amount}`,
+ {
+ headers: {
+ "Content-Type": "application/json",
+ "0x-api-key": "api-key",
+ },
+ },
+ );
+ if (!response.ok) {
+ throw new Error("Failed to fetch quote");
+ }
+ return response.json();
+ };
+ return useQuery({
+ queryKey: ["quote", sellToken],
+ queryFn: fetchQuote,
+ });
+};
From 0dfdca80f78bfcf26a9a15ec6042efa4640802ab Mon Sep 17 00:00:00 2001
From: memosys <82053242+memosys@users.noreply.github.com>
Date: Sun, 21 Apr 2024 17:45:57 +0530
Subject: [PATCH 5/8] save
---
.../apps/forms/allowedOriginsForm.tsx | 2 --
web-portal/frontend/components/swap/Swap.tsx | 17 ++++++++++++-----
2 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/web-portal/frontend/components/apps/forms/allowedOriginsForm.tsx b/web-portal/frontend/components/apps/forms/allowedOriginsForm.tsx
index 0a89dbfa..3bbc775a 100644
--- a/web-portal/frontend/components/apps/forms/allowedOriginsForm.tsx
+++ b/web-portal/frontend/components/apps/forms/allowedOriginsForm.tsx
@@ -16,8 +16,6 @@ import { IRuleType } from "@frontend/utils/types";
export default function AllowedOriginsForm() {
const appId = useParams()?.app as string;
const searchParams = useSearchParams();
- const router = useRouter();
- const path = usePathname();
const rule = searchParams?.get("rule") as string;
const { mutateAsync, isPending, isSuccess } = useUpdateRuleMutation(
appId,
diff --git a/web-portal/frontend/components/swap/Swap.tsx b/web-portal/frontend/components/swap/Swap.tsx
index 96912690..04cbac9b 100644
--- a/web-portal/frontend/components/swap/Swap.tsx
+++ b/web-portal/frontend/components/swap/Swap.tsx
@@ -8,7 +8,7 @@ import { tokenDataAtom } from "@frontend/utils/atoms";
import { IToken } from "@frontend/utils/types";
import { SearchableSelectModal } from "./SearchableSelectModal";
import { useAccount, useBalance, useChainId } from "wagmi";
-import { zeroAddress } from "viem";
+import { formatGwei, zeroAddress } from "viem";
import { chains } from "@frontend/utils/Web3Provider";
import { useQuote } from "@frontend/utils/hooks";
@@ -63,10 +63,11 @@ export default function Swap({ defaultToken }: { defaultToken: IToken }) {
const { data: quote } = useQuote({
sellToken: selectedTokenData?.address,
- amount: String(swapValue * 10 ** 4),
+ amount: String(swapValue * 10 ** selectedTokenData?.decimals),
});
- console.log("quote", quote);
+ console.log(String(swapValue * 10 ** selectedTokenData?.decimals));
+
return (
Number(_.get(tokenBalance, "formatted"))
+ ? "Not enough balance"
+ : undefined
+ }
/>
- ${(Number(swapValue || 0) * 0.01).toFixed(6)}
+ ${(Number(swapValue || 0) * 0.01).toFixed(6)}
- {Number(_.get(tokenBalance, "formatted")).toFixed(6)}
+ {Number(_.get(tokenBalance, "formatted") ?? 0).toFixed(6)}
setSwapValue(Number(_.get(tokenBalance, "formatted")))
From a90c7c0a2296a1a0af53df8cc8d83b3435e2002a Mon Sep 17 00:00:00 2001
From: memosys <82053242+memosys@users.noreply.github.com>
Date: Mon, 22 Apr 2024 14:46:08 +0530
Subject: [PATCH 6/8] basic expectation
---
.../frontend/components/apps/appRuleModal.tsx | 2 +
.../frontend/components/apps/appRules.tsx | 5 +-
.../apps/forms/allowedUserAgentsForm.tsx | 2 +-
.../components/apps/forms/rateLimitForm.tsx | 149 ++++++++++++++++++
4 files changed, 156 insertions(+), 2 deletions(-)
create mode 100644 web-portal/frontend/components/apps/forms/rateLimitForm.tsx
diff --git a/web-portal/frontend/components/apps/appRuleModal.tsx b/web-portal/frontend/components/apps/appRuleModal.tsx
index fc1ebefb..b3f81f5c 100644
--- a/web-portal/frontend/components/apps/appRuleModal.tsx
+++ b/web-portal/frontend/components/apps/appRuleModal.tsx
@@ -10,6 +10,7 @@ import ApprovedChainForm from "./forms/approvedChainsForm";
import AllowedUserAgentsForm from "./forms/allowedUserAgentsForm";
import AllowedOriginsForm from "./forms/allowedOriginsForm";
import _ from "lodash";
+import RateLimitForm from "./forms/rateLimitForm";
export default function CreateAppRuleModal() {
const searchParams = useSearchParams();
@@ -29,6 +30,7 @@ export default function CreateAppRuleModal() {
{shouldOpen === "secret-key" && }
{shouldOpen === "approved-chains" && }
{shouldOpen === "allowed-user-agents" && }
+ {shouldOpen === "rate-limit" && }
);
}
diff --git a/web-portal/frontend/components/apps/appRules.tsx b/web-portal/frontend/components/apps/appRules.tsx
index e5347065..13fe1273 100644
--- a/web-portal/frontend/components/apps/appRules.tsx
+++ b/web-portal/frontend/components/apps/appRules.tsx
@@ -30,7 +30,10 @@ const AppRules: React.FC<{ rule: Partial }> = ({ rule }) => {
color: "white",
}}
>
- {item.value}
+ {item.value
+ ?.replace("P1D", "Daily")
+ .replace("P1W", "Weekly")
+ .replace("P1M", "Monthly")}
));
diff --git a/web-portal/frontend/components/apps/forms/allowedUserAgentsForm.tsx b/web-portal/frontend/components/apps/forms/allowedUserAgentsForm.tsx
index 7e6dd49d..2ce26581 100644
--- a/web-portal/frontend/components/apps/forms/allowedUserAgentsForm.tsx
+++ b/web-portal/frontend/components/apps/forms/allowedUserAgentsForm.tsx
@@ -19,7 +19,7 @@ export default function AllowedUserAgentsForm() {
);
const [value, setValue] = useAtom(existingRuleValuesAtom);
const ruleTypes = useAtomValue(ruleTypesAtom);
- const router = useRouter();
+
const validationRule = _.get(
_.find(ruleTypes, (r: IRuleType) => r.name === rule),
"validationValue",
diff --git a/web-portal/frontend/components/apps/forms/rateLimitForm.tsx b/web-portal/frontend/components/apps/forms/rateLimitForm.tsx
new file mode 100644
index 00000000..864d957a
--- /dev/null
+++ b/web-portal/frontend/components/apps/forms/rateLimitForm.tsx
@@ -0,0 +1,149 @@
+import _ from "lodash";
+import { useAtom, useAtomValue } from "jotai";
+import { existingRuleValuesAtom, ruleTypesAtom } from "@frontend/utils/atoms";
+import { useForm } from "@mantine/form";
+import {
+ Button,
+ Flex,
+ Pill,
+ Stack,
+ Text,
+ Select,
+ NumberInput,
+} from "@mantine/core";
+import { IconPlus } from "@tabler/icons-react";
+import { IRuleType } from "@frontend/utils/types";
+
+import { useUpdateRuleMutation } from "@frontend/utils/hooks";
+import { useSearchParams, useParams } from "next/navigation";
+
+const periodOptions = [
+ { label: "Weekly", value: "P1W" },
+ { label: "Monthly", value: "P1M" },
+ { label: "Daily", value: "P1D" },
+];
+
+export default function RateLimitForm() {
+ const appId = useParams()?.app as string;
+ const searchParams = useSearchParams();
+ const rule = searchParams?.get("rule") as string;
+ const { mutateAsync, isPending, isSuccess } = useUpdateRuleMutation(
+ appId,
+ rule,
+ );
+
+ const [value, setValue] = useAtom(existingRuleValuesAtom);
+ const ruleTypes = useAtomValue(ruleTypesAtom);
+
+ const validationRule = _.get(
+ _.find(ruleTypes, (r: IRuleType) => r.name === rule),
+ "validationValue",
+ );
+ const ruleRegex = new RegExp(validationRule as string);
+ const form = useForm({
+ validate: {
+ requests: (val) => {
+ return _.isInteger(Number(val)) && Number(val) > 0
+ ? null
+ : "Please enter a valid number";
+ },
+ period: (val: string) => {
+ const isValid = periodOptions.some((option) => {
+ return option.value === val;
+ });
+
+ const alreadyExists = value.some((v) => v.includes(val));
+ if (alreadyExists) {
+ return "Period already exists";
+ }
+ return isValid ? null : "Please select a valid period";
+ },
+ },
+ });
+
+ const handleValueRemove = (val: string) =>
+ setValue((current) => current.filter((v) => v !== val));
+
+ const formValidation = () => form.validate();
+
+ const values = value?.map((val) => (
+ handleValueRemove(val)}
+ size="lg"
+ m={2}
+ bg="blue"
+ style={{
+ color: "white",
+ }}
+ >
+ {val
+ ?.replace("P1D", "Daily")
+ .replace("P1W", "Weekly")
+ .replace("P1M", "Monthly")}
+
+ ));
+
+ const handleSubmit = () => {
+ const isValid = value.map((v) => (ruleRegex.test(v) ? true : false));
+ isValid.includes(false)
+ ? form.setErrors({ requests: "Invalid value" })
+ : mutateAsync(value);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {form.errors.requests}
+
+ {form.errors.period}
+
+ {values}
+
+
+
+ );
+}
From de7519c794b22592eb4685038a5b295916f9a853 Mon Sep 17 00:00:00 2001
From: memosys <82053242+memosys@users.noreply.github.com>
Date: Mon, 22 Apr 2024 14:48:21 +0530
Subject: [PATCH 7/8] form reset issue
---
web-portal/frontend/components/apps/forms/rateLimitForm.tsx | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/web-portal/frontend/components/apps/forms/rateLimitForm.tsx b/web-portal/frontend/components/apps/forms/rateLimitForm.tsx
index 864d957a..f46e95a6 100644
--- a/web-portal/frontend/components/apps/forms/rateLimitForm.tsx
+++ b/web-portal/frontend/components/apps/forms/rateLimitForm.tsx
@@ -117,12 +117,11 @@ export default function RateLimitForm() {
if (value.some((v) => v.includes(form.values.period))) {
return form.setErrors({ period: "Period already exists" });
}
-
setValue((current: any) => [
form.values.requests + `/` + form.values.period,
...current,
]),
- form.reset();
+ form.setValues({ requests: "", period: "" });
}}
>
From 236bb6db19f123ec0ebf09f174261be1e350532a Mon Sep 17 00:00:00 2001
From: memosys <82053242+memosys@users.noreply.github.com>
Date: Mon, 22 Apr 2024 14:51:38 +0530
Subject: [PATCH 8/8] minor improvements
---
.../components/apps/forms/allowedOriginsForm.tsx | 12 +++++-------
.../components/apps/forms/allowedUserAgentsForm.tsx | 5 ++++-
.../components/apps/forms/approvedChainsForm.tsx | 3 +--
.../frontend/components/apps/forms/rateLimitForm.tsx | 6 +++++-
.../components/common/SearchableMultiSelect.tsx | 1 +
5 files changed, 16 insertions(+), 11 deletions(-)
diff --git a/web-portal/frontend/components/apps/forms/allowedOriginsForm.tsx b/web-portal/frontend/components/apps/forms/allowedOriginsForm.tsx
index 3bbc775a..8dc9e8f9 100644
--- a/web-portal/frontend/components/apps/forms/allowedOriginsForm.tsx
+++ b/web-portal/frontend/components/apps/forms/allowedOriginsForm.tsx
@@ -3,12 +3,7 @@ import _ from "lodash";
import { useForm, matches } from "@mantine/form";
import { IconPlus } from "@tabler/icons-react";
import { useUpdateRuleMutation } from "@frontend/utils/hooks";
-import {
- useParams,
- usePathname,
- useRouter,
- useSearchParams,
-} from "next/navigation";
+import { useParams, useSearchParams } from "next/navigation";
import { useAtom, useAtomValue } from "jotai";
import { existingRuleValuesAtom, ruleTypesAtom } from "@frontend/utils/atoms";
import { IRuleType } from "@frontend/utils/types";
@@ -36,6 +31,9 @@ export default function AllowedOriginsForm() {
"Enter a valid url that starts with http or https",
),
},
+ initialValues: {
+ url: "",
+ },
});
const handleValueRemove = (val: string) =>
@@ -75,7 +73,7 @@ export default function AllowedOriginsForm() {
onClick={() => {
if (formValidation().hasErrors) return;
setValue((current: any) => [form.values.url, ...current]),
- form.setFieldValue("url", "");
+ form.reset();
}}
>
diff --git a/web-portal/frontend/components/apps/forms/allowedUserAgentsForm.tsx b/web-portal/frontend/components/apps/forms/allowedUserAgentsForm.tsx
index 2ce26581..5336875f 100644
--- a/web-portal/frontend/components/apps/forms/allowedUserAgentsForm.tsx
+++ b/web-portal/frontend/components/apps/forms/allowedUserAgentsForm.tsx
@@ -29,6 +29,9 @@ export default function AllowedUserAgentsForm() {
validate: {
userAgent: matches(ruleRegex, "Enter a valid url user agent string"),
},
+ initialValues: {
+ userAgent: "",
+ },
});
const handleValueRemove = (val: string) =>
@@ -67,7 +70,7 @@ export default function AllowedUserAgentsForm() {
onClick={() => {
if (formValidation().hasErrors) return;
setValue((current: any) => [form.values.userAgent, ...current]),
- form.setFieldValue("userAgent", "");
+ form.reset();
}}
>
diff --git a/web-portal/frontend/components/apps/forms/approvedChainsForm.tsx b/web-portal/frontend/components/apps/forms/approvedChainsForm.tsx
index 7b12e076..a93f3af9 100644
--- a/web-portal/frontend/components/apps/forms/approvedChainsForm.tsx
+++ b/web-portal/frontend/components/apps/forms/approvedChainsForm.tsx
@@ -8,13 +8,12 @@ import { IEndpoint } from "@frontend/utils/types";
import { SearchableMultiSelect } from "@frontend/components/common/SearchableMultiSelect";
import { useUpdateRuleMutation } from "@frontend/utils/hooks";
-import { useSearchParams, useParams, useRouter } from "next/navigation";
+import { useSearchParams, useParams } from "next/navigation";
export default function ApprovedChainForm() {
const list = useAtomValue(endpointsAtom) as IEndpoint[];
const items = _.map(list, "name");
- const router = useRouter();
const appId = useParams()?.app as string;
const searchParams = useSearchParams();
const rule = searchParams?.get("rule") as string;
diff --git a/web-portal/frontend/components/apps/forms/rateLimitForm.tsx b/web-portal/frontend/components/apps/forms/rateLimitForm.tsx
index f46e95a6..15dee657 100644
--- a/web-portal/frontend/components/apps/forms/rateLimitForm.tsx
+++ b/web-portal/frontend/components/apps/forms/rateLimitForm.tsx
@@ -59,6 +59,10 @@ export default function RateLimitForm() {
return isValid ? null : "Please select a valid period";
},
},
+ initialValues: {
+ requests: "",
+ period: "",
+ },
});
const handleValueRemove = (val: string) =>
@@ -121,7 +125,7 @@ export default function RateLimitForm() {
form.values.requests + `/` + form.values.period,
...current,
]),
- form.setValues({ requests: "", period: "" });
+ form.reset();
}}
>
diff --git a/web-portal/frontend/components/common/SearchableMultiSelect.tsx b/web-portal/frontend/components/common/SearchableMultiSelect.tsx
index d1ae19e1..763f2346 100644
--- a/web-portal/frontend/components/common/SearchableMultiSelect.tsx
+++ b/web-portal/frontend/components/common/SearchableMultiSelect.tsx
@@ -39,6 +39,7 @@ export function SearchableMultiSelect({
onRemove={() => handleValueRemove(item)}
bg="blue"
c="#fff"
+ size="lg"
>
{item}