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

Atlas integration for file uploads to IPFS #1687

Merged
merged 3 commits into from
Oct 16, 2024
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 web/.env.devnet-neo.public
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export REACT_APP_DRT_ARBSEPOLIA_SUBGRAPH=https://api.studio.thegraph.com/query/6
export REACT_APP_STATUS_URL=https://kleros-v2-devnet.betteruptime.com/badge
export REACT_APP_GENESIS_BLOCK_ARBSEPOLIA=3084598
export REACT_APP_ARBITRATOR_TYPE=neo
export REACT_APP_ATLAS_URI=http://localhost:3000/graphql
export REACT_APP_ATLAS_URI=http://localhost:3000
export WALLETCONNECT_PROJECT_ID=
export ALCHEMY_API_KEY=
export NODE_OPTIONS='--max-old-space-size=7680'
2 changes: 1 addition & 1 deletion web/.env.devnet-university.public
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export REACT_APP_DRT_ARBSEPOLIA_SUBGRAPH=https://api.studio.thegraph.com/query/6
export REACT_APP_STATUS_URL=https://kleros-v2-devnet.betteruptime.com/badge
export REACT_APP_GENESIS_BLOCK_ARBSEPOLIA=3084598
export REACT_APP_ARBITRATOR_TYPE=university
export REACT_APP_ATLAS_URI=http://localhost:3000/graphql
export REACT_APP_ATLAS_URI=http://localhost:3000
export WALLETCONNECT_PROJECT_ID=
export ALCHEMY_API_KEY=
export NODE_OPTIONS='--max-old-space-size=7680'
2 changes: 1 addition & 1 deletion web/.env.devnet.public
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export REACT_APP_CORE_SUBGRAPH=https://api.studio.thegraph.com/query/61738/klero
export REACT_APP_DRT_ARBSEPOLIA_SUBGRAPH=https://api.studio.thegraph.com/query/61738/kleros-v2-drt-arbisep-devnet/version/latest
export REACT_APP_STATUS_URL=https://kleros-v2-devnet.betteruptime.com/badge
export REACT_APP_GENESIS_BLOCK_ARBSEPOLIA=3084598
export REACT_APP_ATLAS_URI=http://localhost:3000/graphql
export REACT_APP_ATLAS_URI=http://localhost:3000
export WALLETCONNECT_PROJECT_ID=
export ALCHEMY_API_KEY=
export NODE_OPTIONS='--max-old-space-size=7680'
2 changes: 1 addition & 1 deletion web/.env.local.public
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
export REACT_APP_DEPLOYMENT=devnet
export REACT_APP_CORE_SUBGRAPH=http://localhost:8000/subgraphs/name/kleros/kleros-v2-core-local
export REACT_APP_DRT_ARBSEPOLIA_SUBGRAPH=https://api.thegraph.com/subgraphs/name/alcercu/templateregistrydevnet
export REACT_APP_ATLAS_URI=http://localhost:3000/graphql
export REACT_APP_ATLAS_URI=http://localhost:3000
export WALLETCONNECT_PROJECT_ID=
export ALCHEMY_API_KEY=
export NODE_OPTIONS='--max-old-space-size=7680'
2 changes: 1 addition & 1 deletion web/.env.mainnet-neo.public
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export REACT_APP_DRT_ARBMAINNET_SUBGRAPH=https://api.studio.thegraph.com/query/6
export REACT_APP_STATUS_URL=https://kleros-v2-devnet.betteruptime.com/badge
export REACT_APP_GENESIS_BLOCK_ARBMAINNET=190274403
export REACT_APP_ARBITRATOR_TYPE=neo
export REACT_APP_ATLAS_URI=http://localhost:3000/graphql
export REACT_APP_ATLAS_URI=http://localhost:3000
export WALLETCONNECT_PROJECT_ID=
export ALCHEMY_API_KEY=
export NODE_OPTIONS='--max-old-space-size=7680'
2 changes: 1 addition & 1 deletion web/.env.testnet.public
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export REACT_APP_DEPLOYMENT=testnet
export REACT_APP_CORE_SUBGRAPH=https://api.studio.thegraph.com/query/61738/kleros-v2-core-testnet/version/latest
export REACT_APP_DRT_ARBSEPOLIA_SUBGRAPH=https://api.studio.thegraph.com/query/61738/kleros-v2-drt-arbisep-devnet/version/latest
export REACT_APP_STATUS_URL=https://kleros-v2.betteruptime.com/badge
export REACT_APP_ATLAS_URI=http://localhost:3000/graphql
export REACT_APP_ATLAS_URI=http://localhost:3000
export REACT_APP_GENESIS_BLOCK_ARBSEPOLIA=3842783
export WALLETCONNECT_PROJECT_ID=
export ALCHEMY_API_KEY=
Expand Down
101 changes: 0 additions & 101 deletions web/netlify/functions/uploadToIPFS.ts

This file was deleted.

37 changes: 0 additions & 37 deletions web/netlify/middleware/authMiddleware.ts

This file was deleted.

43 changes: 41 additions & 2 deletions web/src/context/AtlasProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ import {
fetchUser,
updateEmail as updateEmailInAtlas,
confirmEmail as confirmEmailInAtlas,
uploadToIpfs,
type User,
type AddUserData,
type UpdateEmailData,
type ConfirmEmailData,
type ConfirmEmailResponse,
Roles,
Products,
} from "utils/atlas";

import { isUndefined } from "src/utils";
Expand All @@ -29,11 +32,13 @@ interface IAtlasProvider {
isAddingUser: boolean;
isFetchingUser: boolean;
isUpdatingUser: boolean;
isUploadingFile: boolean;
user: User | undefined;
userExists: boolean;
authoriseUser: () => void;
addUser: (userSettings: AddUserData) => Promise<boolean>;
updateEmail: (userSettings: UpdateEmailData) => Promise<boolean>;
uploadFile: (file: File, role: Roles) => Promise<string | null>;
confirmEmail: (userSettings: ConfirmEmailData) => Promise<
ConfirmEmailResponse & {
isError: boolean;
Expand All @@ -57,6 +62,7 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) =
const [isAddingUser, setIsAddingUser] = useState(false);
const [isUpdatingUser, setIsUpdatingUser] = useState(false);
const [isVerified, setIsVerified] = useState(false);
const [isUploadingFile, setIsUploadingFile] = useState(false);
const { signMessageAsync } = useSignMessage();

const atlasGqlClient = useMemo(() => {
Expand All @@ -65,7 +71,7 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) =
authorization: `Bearer ${authToken}`,
}
: undefined;
return new GraphQLClient(atlasUri, { headers });
return new GraphQLClient(`${atlasUri}/graphql`, { headers });
}, [authToken]);

/**
Expand Down Expand Up @@ -131,7 +137,7 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) =
// this would change based on the fields we have and what defines a user to be existing
const userExists = useMemo(() => {
if (!user) return false;
return user.email ? true : false;
return !isUndefined(user.email);
}, [user]);

/**
Expand Down Expand Up @@ -208,6 +214,35 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) =
[address, isVerified, setIsUpdatingUser, atlasGqlClient, refetchUser]
);

/**
* @description upload file to ipfs
* @param {File} file - file to be uploaded
* @param {Roles} role - role for which file is being uploaded
* @returns {Promise<string | null>} A promise that resolves to the ipfs cid if file was uploaded successfully else
* null
*/
const uploadFile = useCallback(
async (file: File, role: Roles) => {
try {
if (!address || !isVerified || !atlasUri || !authToken) return null;
setIsUploadingFile(true);

const hash = await uploadToIpfs(
{ baseUrl: atlasUri, authToken },
{ file, name: file.name, role, product: Products.CourtV2 }
);
return hash ? `/ipfs/${hash}` : null;
} catch (err: any) {
// eslint-disable-next-line
console.log("Upload File Error : ", err?.message);
return null;
} finally {
setIsUploadingFile(false);
}
},
[address, isVerified, setIsUploadingFile, authToken]
);

/**
* @description confirms user email in atlas
* @param {ConfirmEmailData} userSettings - object containing data to be sent
Expand Down Expand Up @@ -244,6 +279,8 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) =
updateEmail,
isUpdatingUser,
userExists,
isUploadingFile,
uploadFile,
confirmEmail,
}),
[
Expand All @@ -257,6 +294,8 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) =
updateEmail,
isUpdatingUser,
userExists,
isUploadingFile,
uploadFile,
confirmEmail,
]
)}
Expand Down
54 changes: 30 additions & 24 deletions web/src/pages/Cases/CaseDetails/Evidence/SubmitEvidenceModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { useWalletClient, usePublicClient, useConfig } from "wagmi";

import { Textarea, Button, FileUploader } from "@kleros/ui-components-library";

import { useAtlasProvider } from "context/AtlasProvider";
import { simulateEvidenceModuleSubmitEvidence } from "hooks/contracts/generated";
import { uploadFormDataToIPFS } from "utils/uploadFormDataToIPFS";
import { Roles } from "utils/atlas";
import { wrapWithToast, OPTIONS as toastOptions } from "utils/wrapWithToast";

import EnsureAuth from "components/EnsureAuth";
Expand Down Expand Up @@ -61,23 +62,28 @@ const SubmitEvidenceModal: React.FC<{
const [isSending, setIsSending] = useState(false);
const [message, setMessage] = useState("");
const [file, setFile] = useState<File>();
const { uploadFile } = useAtlasProvider();

const submitEvidence = useCallback(async () => {
setIsSending(true);
const evidenceJSON = await constructEvidence(message, file);

const { request } = await simulateEvidenceModuleSubmitEvidence(wagmiConfig, {
args: [BigInt(evidenceGroup), JSON.stringify(evidenceJSON)],
});

if (!walletClient) return;
await wrapWithToast(async () => await walletClient.writeContract(request), publicClient)
.then(() => {
setMessage("");
close();
})
.finally(() => setIsSending(false));
}, [publicClient, wagmiConfig, walletClient, close, evidenceGroup, file, message, setIsSending]);
try {
setIsSending(true);
const evidenceJSON = await constructEvidence(uploadFile, message, file);

const { request } = await simulateEvidenceModuleSubmitEvidence(wagmiConfig, {
args: [BigInt(evidenceGroup), JSON.stringify(evidenceJSON)],
});

if (!walletClient || !publicClient) return;
await wrapWithToast(async () => await walletClient.writeContract(request), publicClient)
.then(() => {
setMessage("");
close();
})
.finally(() => setIsSending(false));
} catch {
setIsSending(false);
}
}, [publicClient, wagmiConfig, walletClient, close, evidenceGroup, file, message, setIsSending, uploadFile]);

return (
<StyledModal {...{ isOpen }}>
Expand All @@ -96,16 +102,16 @@ const SubmitEvidenceModal: React.FC<{
);
};

const constructEvidence = async (msg: string, file?: File) => {
let fileURI: string | undefined = undefined;
const constructEvidence = async (
uploadFile: (file: File, role: Roles) => Promise<string | null>,
msg: string,
file?: File
) => {
let fileURI: string | null = null;
if (file) {
toast.info("Uploading to IPFS", toastOptions);
const fileFormData = new FormData();
fileFormData.append("data", file, file.name);
fileURI = await uploadFormDataToIPFS(fileFormData).then(async (res) => {
const response = await res.json();
return response["cids"][0];
});
fileURI = await uploadFile(file, Roles.Evidence);
if (!fileURI) throw new Error("Error uploading evidence file");
}
return { name: "Evidence", description: msg, fileURI };
};
Expand Down
Loading
Loading