Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Frontend dApp #17

Merged
merged 64 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
3727c45
dApp initial implementation
matejos Oct 31, 2023
c23d38a
Multi-locking/unlocking
matejos Nov 7, 2023
a2b44d5
Use TanStack Query to refresh data after actions
matejos Nov 10, 2023
bb67ab3
Lowercase countdown text
matejos Nov 13, 2023
9ff74e2
UseInfiniteQuery for alchemy nft paginated request
matejos Nov 13, 2023
a9f074c
New deployment address
matejos Nov 14, 2023
7b511d4
Cardano wallet connection
matejos Nov 22, 2023
0c9546f
Display cardano tokens
matejos Nov 22, 2023
96ff3fb
Cardano locking and unlocking
matejos Nov 23, 2023
86c2ff4
Cardano: map the locks accordingly, prepare withdraw
matejos Nov 24, 2023
ca95761
Cardano: transaction confirmation awaiting
matejos Nov 24, 2023
5df99be
Cardano: Remove `projected-nft-sdk`
matejos Nov 27, 2023
b5d02e8
Cardano: Get NFT metadata from carp
matejos Nov 28, 2023
fc4434c
Cardano: PolicyId formatting and copying onclick
matejos Nov 28, 2023
fe43791
Minor polish
matejos Nov 28, 2023
2e0c8b3
Cardano: Handle transaction rejection
matejos Nov 28, 2023
986a554
Cardano: Address and balance in wallet info
matejos Nov 28, 2023
9df0755
Cardano: Multiple locking
matejos Nov 28, 2023
bf21a68
Cardano: Refactor to support multiple unlock/withdraw
matejos Nov 29, 2023
9a3c9ca
Cardano: Show token image in locked tokens list
matejos Nov 29, 2023
3f640f4
Cardano: Bunch of cosmetic fixes and improvements
matejos Nov 29, 2023
1b8355b
Cardano: Carp update
matejos Nov 30, 2023
49b279a
Cardano: Dialogs visual update
matejos Nov 30, 2023
3b8756c
EVM: Rainbowkit use dark theme
matejos Nov 30, 2023
dc4fd40
Use `envalid`
matejos Dec 1, 2023
f84253f
Cardano: Partial unlock/withdraw
matejos Dec 1, 2023
3ebb2f8
Cardano: 'Show only NFTs' switch
matejos Dec 4, 2023
0572f32
Cardano: Don't show tokens image if no tokens in UTxO have image
matejos Dec 4, 2023
686978f
Expand accordion if it has unlocking or unlocked
matejos Dec 5, 2023
03b3390
EVM: Hide cardmedia if no token has image
matejos Dec 5, 2023
cecf1c5
Readme update about domain whitelisting
matejos Dec 5, 2023
93ae767
Consolidate Blockfrost project IDs into one
matejos Dec 5, 2023
05c9abf
EVM: Dialog flow for approving collection
matejos Dec 5, 2023
d6a24ad
EVM: Buttons displaying conditions
matejos Dec 5, 2023
cbc388d
EVM: Select multiple tokens unlock/withdraw
matejos Dec 6, 2023
117bdb1
EVM: Select multiple tokens lock
matejos Dec 7, 2023
c917029
EVM: Refactoring, polish
matejos Dec 7, 2023
7b4aa16
Cardano: Fix Lucid
matejos Dec 7, 2023
df808e4
Restructuring, clean-up
matejos Dec 7, 2023
d8d852d
Cardano polish
matejos Dec 7, 2023
8161731
Ignore sourcemap warnings
matejos Dec 7, 2023
dab4f38
Fix warnings - unused code
matejos Dec 7, 2023
08dd3eb
Cardano: Lock quantity = 1 only if testnet
matejos Dec 7, 2023
01e9f4c
EVM: Implement metadata API maximum limit
matejos Dec 7, 2023
36d5bd7
Snackbars for various actions and errors
matejos Dec 7, 2023
4b7c347
Fix mistakenly left debugging code
matejos Dec 11, 2023
d9d0edd
Change package name
matejos Dec 11, 2023
2bca3d3
EVM multiple chains support
matejos Dec 11, 2023
f1e2620
EVM: `Select all` button
matejos Dec 11, 2023
c9d07bc
Cardano: `Select all` button
matejos Dec 11, 2023
56d2f13
ChainSelector display icon instead of name
matejos Dec 12, 2023
bd6e41b
Mobile UI adjustments
matejos Dec 12, 2023
b35a9f8
CopyableTypography component
matejos Dec 12, 2023
24553c7
Remove console logs
matejos Dec 12, 2023
4aa76eb
Swap prepare functions for actions to reduce number of API calls
matejos Dec 13, 2023
ba0181b
Avoid magic strings with chainType, assertNever,
matejos Dec 18, 2023
432a798
Extract reserve waiting time into function
matejos Dec 18, 2023
87ca5fe
Remove commented-out code
matejos Dec 18, 2023
320c0aa
Carp client changed amount from number to string
matejos Dec 18, 2023
f6f9904
Cardano: Support any wallet that conforms to cip30
matejos Dec 18, 2023
95121bf
Add comments to datum
matejos Dec 18, 2023
7247dc8
Use types from Carp client
matejos Dec 18, 2023
f5fdda1
Add comment to EVM contract address
matejos Dec 18, 2023
be99158
Resolve remaining PR comments
SebastienGllmt Dec 29, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions dapp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ Run `yarn generate` to generate new hooks when contracts are changed.

A common hook `useGetChainType` is used throughout the dApp to differentiate chain type (Cardano and EVM). If you wish to add a different type of chain (eg. Aptos), look there.

EVM chains specifics are defined in the `src/utils/evm/chains` (supported chains), `src/utils/evm/contracts` (Hololocker contract address) and `src/utils/evm/wagmi` (wagmi providers).
Cardano chain specifics are defined in the `src/utils/cardano/validator` (validator code)
EVM chains specifics are defined in the `src/utils/evm/chains` (supported chains and their constants), `src/utils/evm/contracts` (Hololocker contract address) and `src/utils/evm/wagmi` (wagmi providers).
Cardano chain specifics are defined in the `src/utils/cardano/validator` (validator code) and `src/utils/cardano/constants`.
1 change: 1 addition & 0 deletions dapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"alchemy-sdk": "^2.11.0",
"assert-never": "^1.2.1",
"axios": "^1.5.1",
"cbor": "^9.0.1",
"curve25519-js": "^0.0.4",
Expand Down
13 changes: 10 additions & 3 deletions dapp/src/components/ChainSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
import { Button } from "@mui/material";
import { useChainModal } from "@rainbow-me/rainbowkit";
import { useGetChainType } from "../hooks/useGetChainType";
import { useGetVmType } from "../hooks/useGetVmType";
import { useNetwork } from "wagmi";
import { isChainSupported } from "../utils/evm/chains";
import slugify from "slugify";
import { VmTypes } from "../utils/constants";
import assertNever from "assert-never";

type Props = {
text?: string;
};

export default function ChainSelector({ text }: Props) {
const { openChainModal: openChainModalEVM } = useChainModal();
const chainType = useGetChainType();
const vmType = useGetVmType();
const { chain: chainEVM } = useNetwork();

const handleClickWhenConnected = () => {
if (chainType === "EVM") {
if (vmType === VmTypes.EVM) {
openChainModalEVM?.();
return;
}
if (vmType === VmTypes.Cardano || vmType === VmTypes.None) {
return;
}
assertNever(vmType);
};

if (!chainEVM) {
Expand Down
56 changes: 42 additions & 14 deletions dapp/src/components/ConnectWallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,20 @@ import {
import { useAccountModal, useConnectModal } from "@rainbow-me/rainbowkit";
import React from "react";
import { useAccount } from "wagmi";
import { useGetChainType } from "../hooks/useGetChainType";
import { useGetVmType } from "../hooks/useGetVmType";
import { useModal } from "mui-modal-provider";
import CardanoWalletsDialog from "../dialogs/CardanoWalletsDialog";
import { useDappStore } from "../store";
import WalletInfoDialog from "../dialogs/WalletInfoDialog";
import { formatCardanoAddress } from "../utils/cardano/utils";
import {
connectWallet,
formatCardanoAddress,
getCardanoWallets,
} from "../utils/cardano/utils";
import { formatEVMAddress } from "../utils/evm/utils";
import { VmTypes } from "../utils/constants";
import assertNever from "assert-never";
import InstallWalletDialog from "../dialogs/InstallWalletDialog";

type Props = {
popoverAnchorOrigin?: PopoverOrigin;
Expand All @@ -34,9 +41,10 @@ export default function ConnectWallet({
const { showModal } = useModal();
const { openConnectModal: openConnectModalEVM } = useConnectModal();
const { openAccountModal: openAccountModalEVM } = useAccountModal();
const chainType = useGetChainType();
const vmType = useGetVmType();
const { address: addressEVM } = useAccount();
const address = useDappStore((state) => state.address);
const selectWallet = useDappStore((state) => state.selectWallet);

const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(
null,
Expand All @@ -56,37 +64,57 @@ export default function ConnectWallet({
};

const openCardanoConnect = () => {
const modal = showModal(CardanoWalletsDialog, {
onCancel: () => {
modal.hide();
},
});
const wallets = getCardanoWallets();
if (wallets.length === 0) {
const modal = showModal(InstallWalletDialog, {
onCancel: () => {
modal.hide();
},
});
} else if (wallets.length === 1) {
connectWallet(wallets[0], selectWallet);
} else {
const modal = showModal(CardanoWalletsDialog, {
wallets,
onCancel: () => {
modal.hide();
},
});
}
handleClose();
};

const handleClickWhenConnected = () => {
if (chainType === "EVM") {
if (vmType === VmTypes.None) {
return;
}
if (vmType === VmTypes.EVM) {
openAccountModalEVM?.();
} else if (chainType === "Cardano") {
return;
} else if (vmType === VmTypes.Cardano) {
const modal = showModal(WalletInfoDialog, {
onCancel: () => {
modal.hide();
},
});
return;
}
assertNever(vmType);
};

const open = Boolean(anchorEl);
const id = open ? "chains-popover" : undefined;
const formattedAddress =
chainType === "EVM"
vmType === VmTypes.EVM
? formatEVMAddress(addressEVM)
: chainType === "Cardano"
: vmType === VmTypes.Cardano
? formatCardanoAddress(address)
: "";
: vmType === VmTypes.None
? ""
: assertNever(vmType);
return (
<>
{chainType == null ? (
{vmType === VmTypes.None ? (
<Button aria-describedby={id} onClick={handleClick}>
Connect wallet
</Button>
Expand Down
11 changes: 6 additions & 5 deletions dapp/src/components/IsConnectedWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { Stack, Typography } from "@mui/material";
import { PropsWithChildren } from "react";
import ConnectWallet from "./ConnectWallet";
import { useGetChainType } from "../hooks/useGetChainType";
import { useGetVmType } from "../hooks/useGetVmType";
import { isChainSupported } from "../utils/evm/chains";
import { useNetwork } from "wagmi";
import ChainSelector from "./ChainSelector";
import { VmTypes } from "../utils/constants";

export default function IsConnectedWrapper({ children }: PropsWithChildren) {
const chainType = useGetChainType();
const vmType = useGetVmType();
const { chain } = useNetwork();
const unsupportedChain = isChainSupported(chain?.id);
const supportedChain = isChainSupported(chain?.id);

if (chainType == null) {
if (vmType === VmTypes.None) {
return (
<Stack sx={{ my: 4, gap: 2, alignItems: "center", textAlign: "center" }}>
<Typography>
Expand All @@ -32,7 +33,7 @@ export default function IsConnectedWrapper({ children }: PropsWithChildren) {
);
}

if (chainType === "EVM" && !unsupportedChain) {
if (vmType === VmTypes.EVM && !supportedChain) {
return (
<Stack sx={{ my: 4, gap: 2, alignItems: "center" }}>
<Typography>You are using an unsupported network.</Typography>
Expand Down
16 changes: 13 additions & 3 deletions dapp/src/components/LockNftSection.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
"use client";
import { Stack, Typography } from "@mui/material";
import { useGetChainType } from "../hooks/useGetChainType";
import { useGetVmType } from "../hooks/useGetVmType";
import LockNftListEVM from "./evm/LockNftList";
import LockNftListCardano from "./cardano/LockNftList";
import { VmTypes } from "../utils/constants";
import assertNever from "assert-never";

export default function LockNftSection() {
const chainType = useGetChainType();
const vmType = useGetVmType();

return (
<Stack sx={{ gap: 2, mt: 4, alignItems: "center", width: "100%" }}>
<Typography variant="h3" textAlign={"center"}>
Project a token
</Typography>
{chainType === "EVM" ? <LockNftListEVM /> : <LockNftListCardano />}
{vmType === VmTypes.None ? (
<></>
) : vmType === VmTypes.EVM ? (
<LockNftListEVM />
) : vmType === VmTypes.Cardano ? (
<LockNftListCardano />
) : (
assertNever(vmType)
)}
</Stack>
);
}
14 changes: 9 additions & 5 deletions dapp/src/components/UnlockNftSection.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
"use client";
import { Stack, Typography } from "@mui/material";
import UnlockNftListEVM from "./evm/UnlockNftList";
import { useGetChainType } from "../hooks/useGetChainType";
import { useGetVmType } from "../hooks/useGetVmType";
import UnlockNftListCardano from "./cardano/UnlockNftList";
import { VmTypes } from "../utils/constants";
import assertNever from "assert-never";

export default function UnlockNftSection() {
const chainType = useGetChainType();
const vmType = useGetVmType();
return (
<Stack sx={{ alignItems: "center", gap: 2, width: "100%" }}>
<Typography variant="h3" textAlign={"center"}>
Projected tokens
</Typography>
{chainType === "EVM" ? (
{vmType === VmTypes.None ? (
<></>
) : vmType === VmTypes.EVM ? (
<UnlockNftListEVM />
) : chainType === "Cardano" ? (
) : vmType === VmTypes.Cardano ? (
<UnlockNftListCardano />
) : (
<></>
assertNever(vmType)
)}
</Stack>
);
Expand Down
19 changes: 10 additions & 9 deletions dapp/src/components/evm/UnlockNftList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
Typography,
} from "@mui/material";
import TransactionButton from "../TransactionButton";
import { useWaitForTransaction } from "wagmi";
import { useNetwork, useWaitForTransaction } from "wagmi";
import { useGetLocksEVM } from "../../hooks/evm/useGetLocksEVM";
import { LockInfoEVM, TokenEVM } from "../../utils/evm/types";
import Grid from "@mui/material/Unstable_Grid2";
Expand All @@ -34,11 +34,7 @@ import { useSnackbar } from "notistack";
import { SnackbarMessage } from "../../utils/texts";
import CopyableTypography from "../CopyableTypography";
import { writeContract } from "@wagmi/core";

// average block time on ETH
const blockTime = 12n;
// we shall wait 1.5x the average block time until we try to simulate withdraw txn
const reserveWaitingTime = (blockTime * 3n) / 2n;
import { getChainReserveWaitingTime } from "../../utils/evm/chains";

function UnlockNftCard({
lockInfo,
Expand All @@ -51,6 +47,7 @@ function UnlockNftCard({
isSelected: boolean;
onClick?: (token: TokenEVM) => void;
}) {
const { chain } = useNetwork();
const { enqueueSnackbar } = useSnackbar();
const [isLoadingUnlock, setIsLoadingUnlock] = useState(false);
const [isLoadingWithdraw, setIsLoadingWithdraw] = useState(false);
Expand All @@ -59,7 +56,7 @@ function UnlockNftCard({
const { token, tokenId, nftData } = lockInfo;
let { unlockTime } = lockInfo;
if (unlockTime > 0n) {
unlockTime += reserveWaitingTime;
unlockTime += getChainReserveWaitingTime(chain?.id);
}
const [now, setNow] = useState<number>(new Date().getTime() / 1000);
const queryClient = useQueryClient();
Expand Down Expand Up @@ -208,6 +205,7 @@ function UnlockNftListItem({
selectedTokens: TokenEVM[];
expanded: boolean;
}) {
const { chain } = useNetwork();
const [now, setNow] = useState<number>(new Date().getTime() / 1000);
useInterval(() => {
setNow(new Date().getTime() / 1000);
Expand All @@ -221,7 +219,8 @@ function UnlockNftListItem({
const tokenIdsToWithdraw = locks
.filter(
(lock) =>
lock.unlockTime !== 0n && now > lock.unlockTime + reserveWaitingTime,
lock.unlockTime !== 0n &&
now > lock.unlockTime + getChainReserveWaitingTime(chain?.id),
)
.map((lock) => lock.tokenId);

Expand Down Expand Up @@ -309,6 +308,7 @@ function UnlockNftListItem({
}

export default function UnlockNftList() {
const { chain } = useNetwork();
const [now, setNow] = useState<number>(new Date().getTime() / 1000);
useInterval(() => {
setNow(new Date().getTime() / 1000);
Expand All @@ -332,7 +332,8 @@ export default function UnlockNftList() {
const locksForWithdraw =
locks?.filter(
(lock) =>
lock.unlockTime > 0n && now > lock.unlockTime + reserveWaitingTime,
lock.unlockTime > 0n &&
now > lock.unlockTime + getChainReserveWaitingTime(chain?.id),
) ?? [];
if (locks) {
if (selectingMultipleUnlock) {
Expand Down
41 changes: 12 additions & 29 deletions dapp/src/dialogs/CardanoWalletsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,19 @@ import {
Typography,
} from "@mui/material";
import { useDappStore } from "../store";
import { useModal } from "mui-modal-provider";
import InstallWalletDialog from "./InstallWalletDialog";
import { Check, Close } from "@mui/icons-material";
import { CardanoWalletInfo } from "../utils/cardano/types";
import { cardanoWallets } from "../utils/cardano/constants";
import { connectWallet, getCardanoWallets } from "../utils/cardano/utils";

type CardanoWalletsDialogProps = {
onCancel: () => void;
wallets: ReturnType<typeof getCardanoWallets>;
} & DialogProps;

export default function CardanoWalletsDialog({
onCancel,
wallets,
...props
}: CardanoWalletsDialogProps) {
const { showModal } = useModal();
const selectWallet = useDappStore((state) => state.selectWallet);
const selectedWalletKey = useDappStore((state) => state.selectedWallet);

Expand All @@ -36,27 +34,6 @@ export default function CardanoWalletsDialog({
return false;
};

const connectWallet = async (walletInfo: CardanoWalletInfo) => {
if (typeof window !== "undefined" && window.cardano) {
if (window.cardano[walletInfo.key]) {
try {
const walletApi = await window.cardano[walletInfo.key].enable();
if (walletApi) {
selectWallet(walletInfo.key);
onCancel();
}
} catch (_) {}
} else {
const modal = showModal(InstallWalletDialog, {
walletInfo,
onCancel: () => {
modal.hide();
},
});
}
}
};

return (
<Dialog {...props}>
<Stack
Expand All @@ -74,12 +51,18 @@ export default function CardanoWalletsDialog({
</IconButton>
</DialogActions>
</Stack>
<MenuList sx={{ pb: 0 }}>
{cardanoWallets.map((wallet) => {
<MenuList>
{wallets.map((wallet) => {
const installed = walletInstalled(wallet.key);
const selected = wallet.key === selectedWalletKey;
return (
<MenuItem onClick={() => connectWallet(wallet)} key={wallet.key}>
<MenuItem
onClick={async () => {
await connectWallet(wallet, selectWallet);
onCancel();
}}
key={wallet.key}
>
<Stack
sx={{
flexDirection: "row",
Expand Down
Loading
Loading