diff --git a/front/src/common/types.ts b/front/src/common/types.ts index 16ccc65..ad884aa 100644 --- a/front/src/common/types.ts +++ b/front/src/common/types.ts @@ -14,6 +14,9 @@ export enum TransactionType { DEPLOY_BOT_INPUT, DEPOSIT_ERC20, APPROVE_ERC20, + MINT_ERC20, + MINT_LP_NFT, + DEPOSIT_LP_NFT, BOT_STEP, MANAGER_BOT_INPUT, RELEASE_FUNDS, @@ -145,6 +148,23 @@ export interface ApproveErc20TransactionInfo extends BaseTransactionInfo { amount: string; } +export interface MintErc20TransactionInfo extends BaseTransactionInfo { + type: TransactionType.MINT_ERC20; + tokenAddress: string; + tokenAmount: string; +} + +export interface MintLpNftTransactionInfo extends BaseTransactionInfo { + type: TransactionType.MINT_LP_NFT; + stableAddress: string; + stableAmount: string; +} + +export interface DepositLpNftTransactionInfo extends BaseTransactionInfo { + type: TransactionType.DEPOSIT_LP_NFT; + nftId: string; +} + export interface BetTransactionInfo extends BaseTransactionInfo { type: TransactionType.BET_INPUT; gameId: string; @@ -194,6 +214,9 @@ export type TransactionInfo = | DeployBotTransactionInfo | DepositErc20TransactionInfo | ApproveErc20TransactionInfo + | MintErc20TransactionInfo + | MintLpNftTransactionInfo + | DepositLpNftTransactionInfo | ResignGameTransactionInfo | BotStepTransactionInfo | ManagerBotTransactionInfo diff --git a/front/src/components/BotManager.jsx b/front/src/components/BotManager.jsx index a1aa9ee..46e816a 100644 --- a/front/src/components/BotManager.jsx +++ b/front/src/components/BotManager.jsx @@ -6,11 +6,17 @@ * See the file LICENSE for more information. */ +import { Spacer } from "@nextui-org/react"; import BotListView from "./list/BotList"; import { useAllBots } from "../state/game/hooks"; +import { useLpNfts } from "../state/game/hooks"; import { Text } from "./ui/Text"; import { styled } from "@stitches/react"; import ModalCreateBot from "./modals/ModalCreateBot"; +import ModalMintStables from "./modals/ModalMintStables"; +import ModalMintLpNft from "./modals/ModalMintLpNft"; +import ModalDepositLpNft from "./modals/ModalDepositLpNft"; +import LpNftListView from "./list/LpNftList"; import Separator from "./ui/Separator"; import { StitchesLogoIcon } from "@radix-ui/react-icons"; import { violet } from "@radix-ui/colors"; @@ -24,6 +30,7 @@ import { Spacer } from "@nextui-org/react"; export default () => { const bots = useAllBots(); const dispatch = useDispatch(); + const lpNfts = useLpNfts(); return (
@@ -75,6 +82,39 @@ export default () => { Deploy bot
+ + + +
+ + + + Mint stables + + } + /> + + + Mint LP NFT + + } + /> + + + Deposit LP NFT + + } + /> + +
+ + diff --git a/front/src/components/modals/ModalDepositLpNft.tsx b/front/src/components/modals/ModalDepositLpNft.tsx new file mode 100644 index 0000000..2e01f28 --- /dev/null +++ b/front/src/components/modals/ModalDepositLpNft.tsx @@ -0,0 +1,217 @@ +import { blackA, green, mauve, violet } from "@radix-ui/colors"; +import * as Dialog from "@radix-ui/react-dialog"; +import { Cross2Icon } from "@radix-ui/react-icons"; +import { keyframes, styled } from "@stitches/react"; +import { useWeb3React } from "@web3-react/core"; +import { useState } from "react"; + +import { TransactionType } from "../../common/types"; +import { USDC_ADDRESS_ON_NETWORKS } from "../../ether/chains"; +import { useTokenFromList } from "../../hooks/token"; +import { useActionCreator } from "../../state/game/hooks"; +import AssetDisplay from "../AssetDisplay"; + +export default ({ triggerElement }) => { + const { chainId } = useWeb3React(); + const [tokenId, setTokenId] = useState(""); + const token = useTokenFromList(USDC_ADDRESS_ON_NETWORKS[chainId]); + + const addAction = useActionCreator(); + + const handleMint = async () => { + console.log(`Depositing token ID ${tokenId}`); + const [, wait] = await addAction({ + type: TransactionType.DEPOSIT_LP_NFT, + nftId: tokenId, + }); + await wait; + }; + + return ( + + {triggerElement} + + + + Deposit LP NFT + Deposit LP NFT for testing. + +
+ + + + +
+
+ { + setTokenId(event.target.value); + }} + > +
+ + + + + + + + + + +
+
+
+ ); +}; + +const overlayShow = keyframes({ + "0%": { opacity: 0 }, + "100%": { opacity: 1 }, +}); + +const contentShow = keyframes({ + "0%": { opacity: 0, transform: "translate(-50%, -48%) scale(.96)" }, + "100%": { opacity: 1, transform: "translate(-50%, -50%) scale(1)" }, +}); + +const DialogOverlay = styled(Dialog.Overlay, { + backgroundColor: blackA.blackA9, + position: "fixed", + inset: 0, + animation: `${overlayShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`, +}); + +const DialogContent = styled(Dialog.Content, { + backgroundColor: "white", + borderRadius: 6, + boxShadow: + "hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px", + position: "fixed", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + width: "90vw", + maxWidth: "450px", + maxHeight: "85vh", + padding: 25, + animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`, + "&:focus": { outline: "none" }, +}); + +const DialogTitle = styled(Dialog.Title, { + margin: 0, + fontWeight: 500, + color: mauve.mauve12, + fontSize: 17, +}); + +const DialogDescription = styled(Dialog.Description, { + margin: "10px 0 20px", + color: mauve.mauve11, + fontSize: 15, + lineHeight: 1.5, +}); + +const RightSlot = styled("div", { + marginLeft: "auto", + paddingLeft: 0, + display: "flex", + color: violet.violet11, + "[data-highlighted] > &": { color: "white" }, + "[data-disabled] &": { color: violet.violet4 }, +}); + +const Flex = styled("div", { display: "flex" }); + +const Button = styled("button", { + all: "unset", + display: "inline-flex", + alignItems: "center", + justifyContent: "center", + borderRadius: 4, + padding: "0 15px", + fontSize: 15, + lineHeight: 1, + fontWeight: 500, + height: 35, + + variants: { + variant: { + violet: { + backgroundColor: "white", + color: violet.violet11, + boxShadow: `0 2px 10px ${blackA.blackA7}`, + "&:hover": { backgroundColor: mauve.mauve3 }, + "&:focus": { boxShadow: `0 0 0 2px black` }, + }, + green: { + backgroundColor: green.green4, + color: green.green11, + "&:hover": { backgroundColor: green.green5 }, + "&:focus": { boxShadow: `0 0 0 2px ${green.green7}` }, + }, + }, + }, + + defaultVariants: { + variant: "violet", + }, +}); + +const IconButton = styled("button", { + all: "unset", + fontFamily: "inherit", + borderRadius: "100%", + height: 25, + width: 25, + display: "inline-flex", + alignItems: "center", + justifyContent: "center", + color: violet.violet11, + position: "absolute", + top: 10, + right: 10, + + "&:hover": { backgroundColor: violet.violet4 }, + "&:focus": { boxShadow: `0 0 0 2px ${violet.violet7}` }, +}); + +const Fieldset = styled("fieldset", { + all: "unset", + display: "flex", + gap: 20, + alignItems: "center", + marginBottom: 15, +}); + +const Label = styled("label", { + fontSize: 13, + lineHeight: 1, + marginBottom: 10, + color: violet.violet12, + display: "block", +}); + +const Input = styled("input", { + all: "unset", + width: "100%", + flex: "1", + display: "inline-flex", + alignItems: "center", + justifyContent: "center", + borderRadius: 4, + padding: "0 10px", + fontSize: 15, + lineHeight: 1, + color: violet.violet11, + boxShadow: `0 0 0 1px ${violet.violet7}`, + height: 35, + + "&:focus": { boxShadow: `0 0 0 2px ${violet.violet8}` }, +}); diff --git a/front/src/components/modals/ModalMintLpNft.tsx b/front/src/components/modals/ModalMintLpNft.tsx new file mode 100644 index 0000000..1e219cc --- /dev/null +++ b/front/src/components/modals/ModalMintLpNft.tsx @@ -0,0 +1,246 @@ +import { blackA, green, mauve, violet } from "@radix-ui/colors"; +import * as Dialog from "@radix-ui/react-dialog"; +import { Cross2Icon } from "@radix-ui/react-icons"; +import { keyframes, styled } from "@stitches/react"; +import { useWeb3React } from "@web3-react/core"; +import { useState } from "react"; + +import { TransactionType } from "../../common/types"; +import { CHAINS } from "../../ether/chains"; +import { USDC_ADDRESS_ON_NETWORKS } from "../../ether/chains"; +import { CONTRACTS } from "../../ether/contracts"; +import { useTokenFromList } from "../../hooks/token"; +import { useActionCreator } from "../../state/game/hooks"; +import AssetDisplay from "./../AssetDisplay"; + +export default ({ triggerElement }) => { + const { chainId, account } = useWeb3React(); + const [amount, setAmount] = useState(0); + const token = useTokenFromList(USDC_ADDRESS_ON_NETWORKS[chainId]); + + const addAction = useActionCreator(); + + const handleDeposit = async () => { + console.log(`Approving amount ${amount} tokenAddress ${token.address}`); + + // Get network name + const CHAIN = CHAINS[chainId]; + const networkName = + CHAIN && CHAIN.networkName ? CHAIN.networkName : "localhost"; + + // Fetch abi list + const contracts = CONTRACTS[networkName]; + const abis = + contracts && contracts.InputFacet && contracts.ERC20PortalFacet + ? contracts + : CONTRACTS.localhost; + + const [, wait] = await addAction({ + type: TransactionType.APPROVE_ERC20, + tokenAddress: token.address, + spender: abis.UniV3Staker.address, + amount: amount.toString(), + }); + await wait; + + console.log( + `Minting LP NFT with amount ${amount} tokenAddress ${token.address}` + ); + const [, wait2] = await addAction({ + type: TransactionType.MINT_LP_NFT, + stableAddress: token.address, + stableAmount: amount.toString(), + }); + await wait2; + }; + + return ( + + {triggerElement} + + + + Mint LP NFT + + Mint an LP NFT and deposit stablecoins. + + +
+ + + + +
+
+ { + setAmount(parseInt(event.target.value || "0")); + }} + > +
+ + + + + + + + + + +
+
+
+ ); +}; + +const overlayShow = keyframes({ + "0%": { opacity: 0 }, + "100%": { opacity: 1 }, +}); + +const contentShow = keyframes({ + "0%": { opacity: 0, transform: "translate(-50%, -48%) scale(.96)" }, + "100%": { opacity: 1, transform: "translate(-50%, -50%) scale(1)" }, +}); + +const DialogOverlay = styled(Dialog.Overlay, { + backgroundColor: blackA.blackA9, + position: "fixed", + inset: 0, + animation: `${overlayShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`, +}); + +const DialogContent = styled(Dialog.Content, { + backgroundColor: "white", + borderRadius: 6, + boxShadow: + "hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px", + position: "fixed", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + width: "90vw", + maxWidth: "450px", + maxHeight: "85vh", + padding: 25, + animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`, + "&:focus": { outline: "none" }, +}); + +const DialogTitle = styled(Dialog.Title, { + margin: 0, + fontWeight: 500, + color: mauve.mauve12, + fontSize: 17, +}); + +const DialogDescription = styled(Dialog.Description, { + margin: "10px 0 20px", + color: mauve.mauve11, + fontSize: 15, + lineHeight: 1.5, +}); + +const RightSlot = styled("div", { + marginLeft: "auto", + paddingLeft: 0, + display: "flex", + color: violet.violet11, + "[data-highlighted] > &": { color: "white" }, + "[data-disabled] &": { color: violet.violet4 }, +}); + +const Flex = styled("div", { display: "flex" }); + +const Button = styled("button", { + all: "unset", + display: "inline-flex", + alignItems: "center", + justifyContent: "center", + borderRadius: 4, + padding: "0 15px", + fontSize: 15, + lineHeight: 1, + fontWeight: 500, + height: 35, + + variants: { + variant: { + violet: { + backgroundColor: "white", + color: violet.violet11, + boxShadow: `0 2px 10px ${blackA.blackA7}`, + "&:hover": { backgroundColor: mauve.mauve3 }, + "&:focus": { boxShadow: `0 0 0 2px black` }, + }, + green: { + backgroundColor: green.green4, + color: green.green11, + "&:hover": { backgroundColor: green.green5 }, + "&:focus": { boxShadow: `0 0 0 2px ${green.green7}` }, + }, + }, + }, + + defaultVariants: { + variant: "violet", + }, +}); + +const IconButton = styled("button", { + all: "unset", + fontFamily: "inherit", + borderRadius: "100%", + height: 25, + width: 25, + display: "inline-flex", + alignItems: "center", + justifyContent: "center", + color: violet.violet11, + position: "absolute", + top: 10, + right: 10, + + "&:hover": { backgroundColor: violet.violet4 }, + "&:focus": { boxShadow: `0 0 0 2px ${violet.violet7}` }, +}); + +const Fieldset = styled("fieldset", { + all: "unset", + display: "flex", + gap: 20, + alignItems: "center", + marginBottom: 15, +}); + +const Label = styled("label", { + fontSize: 13, + lineHeight: 1, + marginBottom: 10, + color: violet.violet12, + display: "block", +}); + +const Input = styled("input", { + all: "unset", + width: "100%", + flex: "1", + display: "inline-flex", + alignItems: "center", + justifyContent: "center", + borderRadius: 4, + padding: "0 10px", + fontSize: 15, + lineHeight: 1, + color: violet.violet11, + boxShadow: `0 0 0 1px ${violet.violet7}`, + height: 35, + + "&:focus": { boxShadow: `0 0 0 2px ${violet.violet8}` }, +}); diff --git a/front/src/components/modals/ModalMintStables.tsx b/front/src/components/modals/ModalMintStables.tsx new file mode 100644 index 0000000..aa1bee6 --- /dev/null +++ b/front/src/components/modals/ModalMintStables.tsx @@ -0,0 +1,218 @@ +import { blackA, green, mauve, violet } from "@radix-ui/colors"; +import * as Dialog from "@radix-ui/react-dialog"; +import { Cross2Icon } from "@radix-ui/react-icons"; +import { keyframes, styled } from "@stitches/react"; +import { useWeb3React } from "@web3-react/core"; +import { useState } from "react"; + +import { TransactionType } from "../../common/types"; +import { USDC_ADDRESS_ON_NETWORKS } from "../../ether/chains"; +import { useTokenFromList } from "../../hooks/token"; +import { useActionCreator } from "../../state/game/hooks"; +import AssetDisplay from "./../AssetDisplay"; + +export default ({ triggerElement }) => { + const { chainId } = useWeb3React(); + const [amount, setAmount] = useState(0); + const token = useTokenFromList(USDC_ADDRESS_ON_NETWORKS[chainId]); + + const addAction = useActionCreator(); + + const handleMint = async () => { + console.log(`minting amount ${amount} tokenAddress ${token.address}`); + const [, wait] = await addAction({ + type: TransactionType.MINT_ERC20, + tokenAddress: token.address, + tokenAmount: amount.toString(), + }); + await wait; + }; + + return ( + + {triggerElement} + + + + Mint stablecoins + Mint stablecoins for testing. + +
+ + + + +
+
+ { + setAmount(parseInt(event.target.value || "0")); + }} + > +
+ + + + + + + + + + +
+
+
+ ); +}; + +const overlayShow = keyframes({ + "0%": { opacity: 0 }, + "100%": { opacity: 1 }, +}); + +const contentShow = keyframes({ + "0%": { opacity: 0, transform: "translate(-50%, -48%) scale(.96)" }, + "100%": { opacity: 1, transform: "translate(-50%, -50%) scale(1)" }, +}); + +const DialogOverlay = styled(Dialog.Overlay, { + backgroundColor: blackA.blackA9, + position: "fixed", + inset: 0, + animation: `${overlayShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`, +}); + +const DialogContent = styled(Dialog.Content, { + backgroundColor: "white", + borderRadius: 6, + boxShadow: + "hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px", + position: "fixed", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + width: "90vw", + maxWidth: "450px", + maxHeight: "85vh", + padding: 25, + animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`, + "&:focus": { outline: "none" }, +}); + +const DialogTitle = styled(Dialog.Title, { + margin: 0, + fontWeight: 500, + color: mauve.mauve12, + fontSize: 17, +}); + +const DialogDescription = styled(Dialog.Description, { + margin: "10px 0 20px", + color: mauve.mauve11, + fontSize: 15, + lineHeight: 1.5, +}); + +const RightSlot = styled("div", { + marginLeft: "auto", + paddingLeft: 0, + display: "flex", + color: violet.violet11, + "[data-highlighted] > &": { color: "white" }, + "[data-disabled] &": { color: violet.violet4 }, +}); + +const Flex = styled("div", { display: "flex" }); + +const Button = styled("button", { + all: "unset", + display: "inline-flex", + alignItems: "center", + justifyContent: "center", + borderRadius: 4, + padding: "0 15px", + fontSize: 15, + lineHeight: 1, + fontWeight: 500, + height: 35, + + variants: { + variant: { + violet: { + backgroundColor: "white", + color: violet.violet11, + boxShadow: `0 2px 10px ${blackA.blackA7}`, + "&:hover": { backgroundColor: mauve.mauve3 }, + "&:focus": { boxShadow: `0 0 0 2px black` }, + }, + green: { + backgroundColor: green.green4, + color: green.green11, + "&:hover": { backgroundColor: green.green5 }, + "&:focus": { boxShadow: `0 0 0 2px ${green.green7}` }, + }, + }, + }, + + defaultVariants: { + variant: "violet", + }, +}); + +const IconButton = styled("button", { + all: "unset", + fontFamily: "inherit", + borderRadius: "100%", + height: 25, + width: 25, + display: "inline-flex", + alignItems: "center", + justifyContent: "center", + color: violet.violet11, + position: "absolute", + top: 10, + right: 10, + + "&:hover": { backgroundColor: violet.violet4 }, + "&:focus": { boxShadow: `0 0 0 2px ${violet.violet7}` }, +}); + +const Fieldset = styled("fieldset", { + all: "unset", + display: "flex", + gap: 20, + alignItems: "center", + marginBottom: 15, +}); + +const Label = styled("label", { + fontSize: 13, + lineHeight: 1, + marginBottom: 10, + color: violet.violet12, + display: "block", +}); + +const Input = styled("input", { + all: "unset", + width: "100%", + flex: "1", + display: "inline-flex", + alignItems: "center", + justifyContent: "center", + borderRadius: 4, + padding: "0 10px", + fontSize: 15, + lineHeight: 1, + color: violet.violet11, + boxShadow: `0 0 0 1px ${violet.violet7}`, + height: 35, + + "&:focus": { boxShadow: `0 0 0 2px ${violet.violet8}` }, +}); diff --git a/front/src/ether/chains.js b/front/src/ether/chains.js index a32c3ac..28289d2 100644 --- a/front/src/ether/chains.js +++ b/front/src/ether/chains.js @@ -208,8 +208,8 @@ export const STABLECOIN_ADDRESS_ON_NETWORKS = { 421611: "0xff970a61a04b1ca14834a43f5de4533ebddb5cc8", 5: "0x50b5a3f1cce4a5936e373e6828afdb073c79c1c7", 420: "0x17c8ad02b6eba2dd5aac672394588f8abd432513", - 31337: CartesiToken.address, - 1337: CartesiToken.address, + 31337: "0xea753456f554F59f70CD0E078FBd2FED058Cedcc", + 1337: "0xea753456f554F59f70CD0E078FBd2FED058Cedcc", }; export const DEFAULT_NETWORK_URI = CHAINS[1].networkImg; diff --git a/front/src/state/game/gameSlice.js b/front/src/state/game/gameSlice.js index 6acbe69..5d59550 100644 --- a/front/src/state/game/gameSlice.js +++ b/front/src/state/game/gameSlice.js @@ -107,6 +107,7 @@ export const gameSlice = createSlice({ }, games: {}, bots: {}, + lpNfts: {}, elo: {}, accounts: {}, tournaments: [], @@ -154,11 +155,15 @@ export const gameSlice = createSlice({ setBots: (state, action) => { state.bots = action.payload; }, + setLpNfts: (state, action) => { + state.lpNfts = action.payload; + }, setAppState: (state, action) => { var { elo, game, bots, + lpNfts, accounts, lastProcessedBlock, actionList, @@ -173,6 +178,7 @@ export const gameSlice = createSlice({ !deepEqual(state.elo, elo) || !deepEqual(state.games, game) || !deepEqual(state.bots, bots) || + !deepEqual(state.lpNfts, lpNfts) || !deepEqual(state.accounts, accounts) || !deepEqual(state.tournaments, tournaments) || !deepEqual(state.challenges, challenges) || @@ -186,6 +192,7 @@ export const gameSlice = createSlice({ state.elo = elo; state.games = game; state.bots = bots; + state.lpNfts = lpNfts; state.accounts = accounts; state.tournaments = tournaments; state.lastProcessedBlock = lastProcessedBlock; @@ -297,6 +304,7 @@ export const { setElo, setAccounts, setBots, + setLpNfts, updateGame, addGame, removeGame, diff --git a/front/src/state/game/hooks.ts b/front/src/state/game/hooks.ts index 69e3051..077d751 100644 --- a/front/src/state/game/hooks.ts +++ b/front/src/state/game/hooks.ts @@ -9,7 +9,7 @@ import { TransactionResponse } from "@ethersproject/providers"; import { useWeb3React } from "@web3-react/core"; import { ethers } from "ethers"; -import { useCallback, useMemo } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useDispatch } from "react-redux"; import { TransactionInfo, TransactionType } from "../../common/types"; @@ -44,6 +44,7 @@ import { BotProfile, Challenge, Game, + LpNftProfile, Profile, ProfileType, Throne, @@ -308,6 +309,64 @@ export function useAllBots(showRank = false): BotProfile[] { }); } +// Get all LP NFTs belonging to the user +export function useLpNfts(): LpNftProfile[] { + const { chainId, account } = useWeb3React(); + + const [lpNfts, setLpNfts] = useState([]); + + // Get network name + const CHAIN = CHAINS[chainId]; + const networkName = + CHAIN && CHAIN.networkName ? CHAIN.networkName : "localhost"; + + // Fetch abi list + const contracts = CONTRACTS[networkName]; + const abis = + contracts && contracts.InputFacet && contracts.ERC20PortalFacet + ? contracts + : CONTRACTS.localhost; + + const lpSftContract = useContract(abis.LpSft.address, abis.LpSft.abi); + + useEffect(() => { + const callFetch = async () => { + if (!lpSftContract) return; + + const userLpNfts = []; + + const tokenIds = await lpSftContract.getTokenIds(account); + for (const tokenId of tokenIds) { + const tokenUri = await lpSftContract.uri(tokenId); + + // Decode the token URI + const tokenUriResponse = await fetch(tokenUri); + const tokenMetadata = await tokenUriResponse.json(); + + const tokenName = tokenMetadata.name; + const tokenDescription = tokenMetadata.description; + const tokenImageUri = tokenMetadata.image; + + const lpNft = { + address: lpSftContract.address, + chainId: chainId, + tokenId: tokenId, + name: tokenName, + description: tokenDescription, + imageUri: tokenImageUri, + }; + userLpNfts.push(lpNft); + } + + setLpNfts(userLpNfts); + }; + + callFetch().catch(console.error); + }, [chainId, lpSftContract]); + + return lpNfts; +} + //get all user profiles //similar to useAllBots export function useAllUsers(showRank = false): UserProfile[] { @@ -711,12 +770,19 @@ export function useActionCreator(): ( const dispatch = useDispatch(); const addAction = useAddAction(); const addTransaction = useTransactionAdder(); - // TODO: Handle DAPP_ADDRESSES[networkName] or CONTRACTS[networkName] not defined + // TODO: Handle dappAddress or abis not defined const contract = useContract(dappAddress, abis.InputFacet.abi); const erc20PortalContract = useContract( dappAddress, abis.ERC20PortalFacet.abi ); + const uniV3StakerContract = useContract( + abis.UniV3Staker.address, + abis.UniV3Staker.abi + ); + const daiContract = useContract(abis.DAI.address, abis.DAI.abi); + const usdcContract = useContract(abis.USDC.address, abis.USDC.abi); + const usdtContract = useContract(abis.USDT.address, abis.USDT.abi); return useCallback( async (info: TransactionInfo) => { @@ -730,7 +796,9 @@ export function useActionCreator(): ( id: id, type: info.type == TransactionType.APPROVE_ERC20 || - info.type == TransactionType.DEPOSIT_ERC20 + info.type == TransactionType.DEPOSIT_ERC20 || + info.type == TransactionType.MINT_ERC20 || + info.type == TransactionType.MINT_LP_NFT ? ActionType.TRANSACTION : ActionType.INPUT, transactionInfo: info, @@ -1007,6 +1075,73 @@ export function useActionCreator(): ( erc20Amount ); break; + case TransactionType.MINT_ERC20: { + const { tokenAddress, tokenAmount } = info; + + // Get the stable contract + let stableContract; + if (tokenAddress.toLowerCase() == daiContract.address.toLowerCase()) + stableContract = daiContract; + else if ( + tokenAddress.toLowerCase() == usdcContract.address.toLowerCase() + ) + stableContract = usdcContract; + else if ( + tokenAddress.toLowerCase() == usdtContract.address.toLowerCase() + ) + stableContract = usdtContract; + + // TODO: More efficient decimal handling + const decimals = await stableContract.decimals(); + + // Calculate ERC20 amount + var erc20Amount = ethers.BigNumber.from( + ethers.utils.parseUnits(tokenAmount, decimals) + ); + + // Mint tokens + result = await stableContract.mint( + await stableContract.signer.getAddress(), + erc20Amount + ); + + break; + } + case TransactionType.MINT_LP_NFT: { + const { stableAddress, stableAmount } = info; + + // Get the stable index + let stableIndex; + if ( + stableAddress.toLowerCase() == daiContract.address.toLowerCase() + ) + stableIndex = 0; + else if ( + stableAddress.toLowerCase() == usdcContract.address.toLowerCase() + ) + stableIndex = 1; + else if ( + stableAddress.toLowerCase() == usdtContract.address.toLowerCase() + ) + stableIndex = 2; + + // Mint the LP NFT + result = await uniV3StakerContract.stakeNFTOneStable( + stableIndex, + stableAmount + ); + + break; + } + case TransactionType.DEPOSIT_LP_NFT: { + const { nftId } = info; + + alert(`Depositing token ${nftId}`); + + //result = await uniV3StakerContract.stakeNFTOneToken(stableIndex, stableAmount); + + break; + } default: break; } @@ -1032,7 +1167,9 @@ export function useActionCreator(): ( id: id, type: info.type == TransactionType.APPROVE_ERC20 || - info.type == TransactionType.DEPOSIT_ERC20 + info.type == TransactionType.DEPOSIT_ERC20 || + info.type == TransactionType.MINT_ERC20 || + info.type == TransactionType.MINT_LP_NFT ? ActionType.TRANSACTION : ActionType.INPUT, transactionInfo: info, diff --git a/front/src/state/game/types.ts b/front/src/state/game/types.ts index aeb66f3..c9f8f85 100644 --- a/front/src/state/game/types.ts +++ b/front/src/state/game/types.ts @@ -141,6 +141,15 @@ export interface Balance { amount: number; } +export interface LpNftProfile { + address: string; + chainId: number; + tokenId: string; + name: string; + description: string; + imageUri: string; +} + export enum ProfileType { HUMAN, BOT, diff --git a/front/src/utils/lists/ultrachess.tokenlists.json b/front/src/utils/lists/ultrachess.tokenlists.json index 59e5de3..b31eaa5 100644 --- a/front/src/utils/lists/ultrachess.tokenlists.json +++ b/front/src/utils/lists/ultrachess.tokenlists.json @@ -49,7 +49,7 @@ }, { "name": "USDC", - "address": "0x7f8c8f5f1eefecb2bcbda3c2e183df9945e00e9d", + "address": "0xea753456f554F59f70CD0E078FBd2FED058Cedcc", "symbol": "USDC", "decimals": 6, "chainId": 31337,