Skip to content

Commit 6f58f20

Browse files
authored
Merge pull request #1687 from kleros/feat/atlas-ipfs-upload
Atlas integration for file uploads to IPFS
2 parents 9382b7f + 96a49cc commit 6f58f20

13 files changed

+146
-179
lines changed

web/.env.devnet-neo.public

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export REACT_APP_DRT_ARBSEPOLIA_SUBGRAPH=https://api.studio.thegraph.com/query/6
55
export REACT_APP_STATUS_URL=https://kleros-v2-devnet.betteruptime.com/badge
66
export REACT_APP_GENESIS_BLOCK_ARBSEPOLIA=3084598
77
export REACT_APP_ARBITRATOR_TYPE=neo
8-
export REACT_APP_ATLAS_URI=http://localhost:3000/graphql
8+
export REACT_APP_ATLAS_URI=http://localhost:3000
99
export WALLETCONNECT_PROJECT_ID=
1010
export ALCHEMY_API_KEY=
1111
export NODE_OPTIONS='--max-old-space-size=7680'

web/.env.devnet-university.public

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export REACT_APP_DRT_ARBSEPOLIA_SUBGRAPH=https://api.studio.thegraph.com/query/6
55
export REACT_APP_STATUS_URL=https://kleros-v2-devnet.betteruptime.com/badge
66
export REACT_APP_GENESIS_BLOCK_ARBSEPOLIA=3084598
77
export REACT_APP_ARBITRATOR_TYPE=university
8-
export REACT_APP_ATLAS_URI=http://localhost:3000/graphql
8+
export REACT_APP_ATLAS_URI=http://localhost:3000
99
export WALLETCONNECT_PROJECT_ID=
1010
export ALCHEMY_API_KEY=
1111
export NODE_OPTIONS='--max-old-space-size=7680'

web/.env.devnet.public

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export REACT_APP_CORE_SUBGRAPH=https://api.studio.thegraph.com/query/61738/klero
44
export REACT_APP_DRT_ARBSEPOLIA_SUBGRAPH=https://api.studio.thegraph.com/query/61738/kleros-v2-drt-arbisep-devnet/version/latest
55
export REACT_APP_STATUS_URL=https://kleros-v2-devnet.betteruptime.com/badge
66
export REACT_APP_GENESIS_BLOCK_ARBSEPOLIA=3084598
7-
export REACT_APP_ATLAS_URI=http://localhost:3000/graphql
7+
export REACT_APP_ATLAS_URI=http://localhost:3000
88
export WALLETCONNECT_PROJECT_ID=
99
export ALCHEMY_API_KEY=
1010
export NODE_OPTIONS='--max-old-space-size=7680'

web/.env.local.public

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
export REACT_APP_DEPLOYMENT=devnet
33
export REACT_APP_CORE_SUBGRAPH=http://localhost:8000/subgraphs/name/kleros/kleros-v2-core-local
44
export REACT_APP_DRT_ARBSEPOLIA_SUBGRAPH=https://api.thegraph.com/subgraphs/name/alcercu/templateregistrydevnet
5-
export REACT_APP_ATLAS_URI=http://localhost:3000/graphql
5+
export REACT_APP_ATLAS_URI=http://localhost:3000
66
export WALLETCONNECT_PROJECT_ID=
77
export ALCHEMY_API_KEY=
88
export NODE_OPTIONS='--max-old-space-size=7680'

web/.env.mainnet-neo.public

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export REACT_APP_DRT_ARBMAINNET_SUBGRAPH=https://api.studio.thegraph.com/query/6
55
export REACT_APP_STATUS_URL=https://kleros-v2-devnet.betteruptime.com/badge
66
export REACT_APP_GENESIS_BLOCK_ARBMAINNET=190274403
77
export REACT_APP_ARBITRATOR_TYPE=neo
8-
export REACT_APP_ATLAS_URI=http://localhost:3000/graphql
8+
export REACT_APP_ATLAS_URI=http://localhost:3000
99
export WALLETCONNECT_PROJECT_ID=
1010
export ALCHEMY_API_KEY=
1111
export NODE_OPTIONS='--max-old-space-size=7680'

web/.env.testnet.public

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export REACT_APP_DEPLOYMENT=testnet
33
export REACT_APP_CORE_SUBGRAPH=https://api.studio.thegraph.com/query/61738/kleros-v2-core-testnet/version/latest
44
export REACT_APP_DRT_ARBSEPOLIA_SUBGRAPH=https://api.studio.thegraph.com/query/61738/kleros-v2-drt-arbisep-devnet/version/latest
55
export REACT_APP_STATUS_URL=https://kleros-v2.betteruptime.com/badge
6-
export REACT_APP_ATLAS_URI=http://localhost:3000/graphql
6+
export REACT_APP_ATLAS_URI=http://localhost:3000
77
export REACT_APP_GENESIS_BLOCK_ARBSEPOLIA=3842783
88
export WALLETCONNECT_PROJECT_ID=
99
export ALCHEMY_API_KEY=

web/netlify/functions/uploadToIPFS.ts

-101
This file was deleted.

web/netlify/middleware/authMiddleware.ts

-37
This file was deleted.

web/src/context/AtlasProvider.tsx

+41-2
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@ import {
1414
fetchUser,
1515
updateEmail as updateEmailInAtlas,
1616
confirmEmail as confirmEmailInAtlas,
17+
uploadToIpfs,
1718
type User,
1819
type AddUserData,
1920
type UpdateEmailData,
2021
type ConfirmEmailData,
2122
type ConfirmEmailResponse,
23+
Roles,
24+
Products,
2225
} from "utils/atlas";
2326

2427
import { isUndefined } from "src/utils";
@@ -29,11 +32,13 @@ interface IAtlasProvider {
2932
isAddingUser: boolean;
3033
isFetchingUser: boolean;
3134
isUpdatingUser: boolean;
35+
isUploadingFile: boolean;
3236
user: User | undefined;
3337
userExists: boolean;
3438
authoriseUser: () => void;
3539
addUser: (userSettings: AddUserData) => Promise<boolean>;
3640
updateEmail: (userSettings: UpdateEmailData) => Promise<boolean>;
41+
uploadFile: (file: File, role: Roles) => Promise<string | null>;
3742
confirmEmail: (userSettings: ConfirmEmailData) => Promise<
3843
ConfirmEmailResponse & {
3944
isError: boolean;
@@ -57,6 +62,7 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) =
5762
const [isAddingUser, setIsAddingUser] = useState(false);
5863
const [isUpdatingUser, setIsUpdatingUser] = useState(false);
5964
const [isVerified, setIsVerified] = useState(false);
65+
const [isUploadingFile, setIsUploadingFile] = useState(false);
6066
const { signMessageAsync } = useSignMessage();
6167

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

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

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

217+
/**
218+
* @description upload file to ipfs
219+
* @param {File} file - file to be uploaded
220+
* @param {Roles} role - role for which file is being uploaded
221+
* @returns {Promise<string | null>} A promise that resolves to the ipfs cid if file was uploaded successfully else
222+
* null
223+
*/
224+
const uploadFile = useCallback(
225+
async (file: File, role: Roles) => {
226+
try {
227+
if (!address || !isVerified || !atlasUri || !authToken) return null;
228+
setIsUploadingFile(true);
229+
230+
const hash = await uploadToIpfs(
231+
{ baseUrl: atlasUri, authToken },
232+
{ file, name: file.name, role, product: Products.CourtV2 }
233+
);
234+
return hash ? `/ipfs/${hash}` : null;
235+
} catch (err: any) {
236+
// eslint-disable-next-line
237+
console.log("Upload File Error : ", err?.message);
238+
return null;
239+
} finally {
240+
setIsUploadingFile(false);
241+
}
242+
},
243+
[address, isVerified, setIsUploadingFile, authToken]
244+
);
245+
211246
/**
212247
* @description confirms user email in atlas
213248
* @param {ConfirmEmailData} userSettings - object containing data to be sent
@@ -244,6 +279,8 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) =
244279
updateEmail,
245280
isUpdatingUser,
246281
userExists,
282+
isUploadingFile,
283+
uploadFile,
247284
confirmEmail,
248285
}),
249286
[
@@ -257,6 +294,8 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) =
257294
updateEmail,
258295
isUpdatingUser,
259296
userExists,
297+
isUploadingFile,
298+
uploadFile,
260299
confirmEmail,
261300
]
262301
)}

web/src/pages/Cases/CaseDetails/Evidence/SubmitEvidenceModal.tsx

+30-24
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import { useWalletClient, usePublicClient, useConfig } from "wagmi";
77

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

10+
import { useAtlasProvider } from "context/AtlasProvider";
1011
import { simulateEvidenceModuleSubmitEvidence } from "hooks/contracts/generated";
11-
import { uploadFormDataToIPFS } from "utils/uploadFormDataToIPFS";
12+
import { Roles } from "utils/atlas";
1213
import { wrapWithToast, OPTIONS as toastOptions } from "utils/wrapWithToast";
1314

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

6567
const submitEvidence = useCallback(async () => {
66-
setIsSending(true);
67-
const evidenceJSON = await constructEvidence(message, file);
68-
69-
const { request } = await simulateEvidenceModuleSubmitEvidence(wagmiConfig, {
70-
args: [BigInt(evidenceGroup), JSON.stringify(evidenceJSON)],
71-
});
72-
73-
if (!walletClient) return;
74-
await wrapWithToast(async () => await walletClient.writeContract(request), publicClient)
75-
.then(() => {
76-
setMessage("");
77-
close();
78-
})
79-
.finally(() => setIsSending(false));
80-
}, [publicClient, wagmiConfig, walletClient, close, evidenceGroup, file, message, setIsSending]);
68+
try {
69+
setIsSending(true);
70+
const evidenceJSON = await constructEvidence(uploadFile, message, file);
71+
72+
const { request } = await simulateEvidenceModuleSubmitEvidence(wagmiConfig, {
73+
args: [BigInt(evidenceGroup), JSON.stringify(evidenceJSON)],
74+
});
75+
76+
if (!walletClient || !publicClient) return;
77+
await wrapWithToast(async () => await walletClient.writeContract(request), publicClient)
78+
.then(() => {
79+
setMessage("");
80+
close();
81+
})
82+
.finally(() => setIsSending(false));
83+
} catch {
84+
setIsSending(false);
85+
}
86+
}, [publicClient, wagmiConfig, walletClient, close, evidenceGroup, file, message, setIsSending, uploadFile]);
8187

8288
return (
8389
<StyledModal {...{ isOpen }}>
@@ -96,16 +102,16 @@ const SubmitEvidenceModal: React.FC<{
96102
);
97103
};
98104

99-
const constructEvidence = async (msg: string, file?: File) => {
100-
let fileURI: string | undefined = undefined;
105+
const constructEvidence = async (
106+
uploadFile: (file: File, role: Roles) => Promise<string | null>,
107+
msg: string,
108+
file?: File
109+
) => {
110+
let fileURI: string | null = null;
101111
if (file) {
102112
toast.info("Uploading to IPFS", toastOptions);
103-
const fileFormData = new FormData();
104-
fileFormData.append("data", file, file.name);
105-
fileURI = await uploadFormDataToIPFS(fileFormData).then(async (res) => {
106-
const response = await res.json();
107-
return response["cids"][0];
108-
});
113+
fileURI = await uploadFile(file, Roles.Evidence);
114+
if (!fileURI) throw new Error("Error uploading evidence file");
109115
}
110116
return { name: "Evidence", description: msg, fileURI };
111117
};

0 commit comments

Comments
 (0)