Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"private": true,
"version": "0.0.0",
"engines": {
"node": "18",
"node": "20",
"pnpm": "9"
},
"packageManager": "pnpm@9.6.0",
Expand Down
17 changes: 9 additions & 8 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,30 @@
"@ethfs/deploy": "workspace:*",
"@ethfs/ponder": "workspace:*",
"@ponder/client": "0.9.0-next.10",
"@rainbow-me/rainbowkit": "^1.3.1",
"@rainbow-me/rainbowkit": "^2.2.4",
"@tanstack/react-query": "^5.66.9",
"fflate": "^0.7.4",
"luxon": "^3.4.4",
"mime": "^4.0.1",
"next": "14.0.4",
"postgres": "^3.4.3",
"react": "^18",
"react-dom": "^18",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-toastify": "^9.1.3",
"tailwind-merge": "^2.1.0",
"viem": "^1.20.0",
"wagmi": "^1.4.12",
"viem": "^2.23.5",
"wagmi": "^2.14.12",
"zustand": "^4.4.7"
},
"devDependencies": {
"@types/luxon": "^3.3.7",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"autoprefixer": "^10.0.1",
"eslint": "^8",
"eslint-config-next": "14.0.4",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
"typescript": "^5.7.3"
}
}
64 changes: 28 additions & 36 deletions packages/web/src/EthereumProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,57 +7,49 @@ import {
lightTheme,
RainbowKitProvider,
} from "@rainbow-me/rainbowkit";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactNode } from "react";
import { configureChains, createConfig, WagmiConfig } from "wagmi";
import { jsonRpcProvider } from "wagmi/providers/jsonRpc";
import { publicProvider } from "wagmi/providers/public";
import { Chain } from "viem";
import { createConfig, http, WagmiProvider } from "wagmi";

import { supportedChains } from "./supportedChains";

const { chains, publicClient } = configureChains(
supportedChains.map((c) => c.chain),
[
jsonRpcProvider({
rpc: (chain) => {
// TODO: simplify, this feels wrong/ugly (maybe better in v2?)
const supportedChain = supportedChains.find(
(c) => c.chain.id === chain.id,
);
if (!supportedChain) return null;
return {
http: supportedChain.rpcUrl,
};
},
}),
publicProvider(),
],
const chains = supportedChains.map((c) => c.chain) as unknown as readonly [
Chain,
...Chain[],
];

const transports = Object.fromEntries(
supportedChains.map((c) => [c.chain.id, http(c.rpcUrl)]),
);

const { connectors } = getDefaultWallets({
appName: "EthFS",
projectId: "bbc87dca59c4e2ac827da9083052f194",
chains,
});

const wagmiConfig = createConfig({
autoConnect: true,
export const wagmiConfig = createConfig({
chains,
transports,
connectors,
publicClient,
});

const queryClient = new QueryClient();

export function EthereumProviders({ children }: { children: ReactNode }) {
return (
<WagmiConfig config={wagmiConfig}>
<RainbowKitProvider
chains={chains}
theme={lightTheme({
borderRadius: "none",
accentColor: "#57534e",
fontStack: "system",
})}
>
{children}
</RainbowKitProvider>
</WagmiConfig>
<QueryClientProvider client={queryClient}>
<WagmiProvider config={wagmiConfig}>
<RainbowKitProvider
theme={lightTheme({
borderRadius: "none",
accentColor: "#57534e",
fontStack: "system",
})}
>
{children}
</RainbowKitProvider>
</WagmiProvider>
</QueryClientProvider>
);
}
47 changes: 17 additions & 30 deletions packages/web/src/WriteButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useConnectModal } from "@rainbow-me/rainbowkit";
import React, { ComponentProps, ReactNode, useState } from "react";
import { useAccount, useNetwork, useSwitchNetwork } from "wagmi";
import { useAccount, useSwitchChain } from "wagmi";

import { useIsMounted } from "./useIsMounted";
import { usePromise } from "./usePromise";
Expand All @@ -27,11 +27,8 @@ export const WriteButton = React.forwardRef<HTMLButtonElement, Props>(
) => {
const isMounted = useIsMounted();
const { openConnectModal, connectModalOpen } = useConnectModal();
const { isConnected, isConnecting } = useAccount();
const { chain } = useNetwork();
const { switchNetwork, isLoading: isSwitchingNetwork } = useSwitchNetwork({
chainId,
});
const { isConnected, isConnecting, chain } = useAccount();
const { switchChain, isPending: isSwitchingChain } = useSwitchChain();

const [writePromise, setWritePromise] = useState<Promise<void> | null>(
null,
Expand Down Expand Up @@ -77,30 +74,20 @@ export const WriteButton = React.forwardRef<HTMLButtonElement, Props>(
}

if (chain != null && chain.id !== chainId) {
if (switchNetwork) {
return render({
...buttonProps,
ref,
type,
"aria-label": "Switch network",
"aria-busy": isSwitchingNetwork,
onClick: (event) => {
if (event.defaultPrevented) return;
if (event.currentTarget.ariaBusy === "true") return;
if (event.currentTarget.ariaDisabled === "true") return;
event.preventDefault();
switchNetwork();
},
});
} else {
return render({
...buttonProps,
ref,
type,
"aria-label": "Wrong network",
"aria-disabled": true,
});
}
return render({
...buttonProps,
ref,
type,
"aria-label": "Switch network",
"aria-busy": isSwitchingChain,
onClick: (event) => {
if (event.defaultPrevented) return;
if (event.currentTarget.ariaBusy === "true") return;
if (event.currentTarget.ariaDisabled === "true") return;
event.preventDefault();
switchChain({ chainId });
},
});
}

return render({
Expand Down
13 changes: 9 additions & 4 deletions packages/web/src/app/[chain]/migrate-v1/createFile.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import IFileStoreAbi from "@ethfs/contracts/out/IFileStore.sol/IFileStore.abi.json";
import deploys from "@ethfs/deploy/deploys.json";
import { Hex, stringToHex, TransactionReceipt } from "viem";
import { readContract, waitForTransaction, writeContract } from "wagmi/actions";
import {
readContract,
waitForTransactionReceipt,
writeContract,
} from "wagmi/actions";

import { wagmiConfig } from "../../../EthereumProviders";
import { File } from "./getFiles";

export async function createFile(
Expand All @@ -14,7 +19,7 @@ export async function createFile(
const deploy = deploys[chainId];

onProgress("Checking filename…");
const fileExists = await readContract({
const fileExists = await readContract(wagmiConfig, {
chainId,
address: deploy.contracts.FileStore.address,
abi: IFileStoreAbi,
Expand All @@ -30,7 +35,7 @@ export async function createFile(
// TODO: add progress messages for long running requests
// https://github.com/holic/a-fundamental-dispute/blob/f83ea42fa60c3b8667f6b0eb03a009d264219ba6/packages/app/src/MintButton.tsx#L118-L131

const { hash: tx } = await writeContract({
const tx = await writeContract(wagmiConfig, {
chainId,
address: deploy.contracts.FileStore.address,
abi: IFileStoreAbi,
Expand All @@ -51,7 +56,7 @@ export async function createFile(
console.log("create file tx", tx);

onProgress(`Waiting for transaction…`);
const receipt = await waitForTransaction({
const receipt = await waitForTransactionReceipt(wagmiConfig, {
chainId,
hash: tx,
});
Expand Down
32 changes: 13 additions & 19 deletions packages/web/src/file-explorer/FileExplorer.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"use client";

import { useQuery } from "@tanstack/react-query";
import { DateTime } from "luxon";
import Link from "next/link";
import React, { useCallback, useMemo, useState } from "react";
import React, { useCallback, useState } from "react";
import { twMerge } from "tailwind-merge";

import { useChain } from "../ChainContext";
Expand All @@ -12,8 +13,6 @@ import { SearchIcon } from "../icons/SearchIcon";
import { PendingPlaceholder } from "../PendingPlaceholder";
import { pluralize } from "../pluralize";
import { UIWindow } from "../UIWindow";
import { useIsMounted } from "../useIsMounted";
import { usePromise } from "../usePromise";
import { useStore as useWindowStackStore } from "../useWindowStack";
import {
FileCreated,
Expand All @@ -26,26 +25,23 @@ import { FileViewer } from "./FileViewer";

export function FileExplorer() {
const chain = useChain();
const isMounted = useIsMounted();
const [searchQuery, setSearchQuery] = useState("");
const [currentFile, setCurrentFile] = useState<OnchainFile | null>(null);
const focusWindow = useWindowStackStore((state) => state.focusWindow);

// TODO: switch to useQuery for better caching
const files = usePromise(
useMemo(() => {
if (!isMounted) return;
const files = useQuery({
queryKey: ["files", chain.id, searchQuery],
queryFn: async () => {
return fetch(
`${process.env.NEXT_PUBLIC_PONDER_URL}/api/${
chain.id
}/files?${new URLSearchParams({
filename: searchQuery,
})}`,
).then((res) => res.json() as Promise<OnchainFile[]>);
}, [chain.id, isMounted, searchQuery]),
);

// TODO: refetch on interval
},
refetchInterval: 4000,
});

const resetCurrentFile = useCallback(() => setCurrentFile(null), []);

Expand All @@ -71,8 +67,8 @@ export function FileExplorer() {
statusBar={
<>
<div>
{files.status === "fulfilled"
? pluralize(files.value.length, "file", "files")
{files.isSuccess
? pluralize(files.data.length, "file", "files")
: null}
</div>
<div>{chain.name}</div>
Expand All @@ -94,14 +90,12 @@ export function FileExplorer() {
<div
className={twMerge(
`flex-grow py-1`,
files.status === "pending"
? "opacity-50 pointer-events-none"
: null,
files.isPending ? "opacity-50 pointer-events-none" : null,
)}
>
{files.status === "fulfilled" ? (
{files.isSuccess ? (
<>
{files.value.map((file) => (
{files.data.map((file) => (
<FileListRow
key={file.filename}
className={twMerge(
Expand Down
13 changes: 8 additions & 5 deletions packages/web/src/file-explorer/FileViewer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import deploys from "@ethfs/deploy/deploys.json";
import { useQuery } from "@tanstack/react-query";
import { DateTime } from "luxon";
import React from "react";
import { useQuery } from "wagmi";

import { useChain } from "../ChainContext";
import { OnchainFile } from "../common";
Expand All @@ -15,10 +15,13 @@ type Props = {
};

function FileViewerThumbnail({ file }: { file: OnchainFile }) {
const { data: fetchResult } = useQuery(["file", file.filename], async () => {
return fetch(`/api/${file.chainId}/files/${file.filename}/contents`).then(
(res) => res.json() as Promise<{ contents: string }>,
);
const { data: fetchResult } = useQuery({
queryKey: ["file", file.filename],
queryFn: async () => {
return fetch(`/api/${file.chainId}/files/${file.filename}/contents`).then(
(res) => res.json() as Promise<{ contents: string }>,
);
},
});

if (!fetchResult) {
Expand Down
Loading
Loading