Skip to content

Commit d24d9de

Browse files
add: lighthouse, akave sdk (#13)
1 parent f48c061 commit d24d9de

File tree

15 files changed

+891
-24
lines changed

15 files changed

+891
-24
lines changed

Diff for: apps/storybook/package.json

+6
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
"@ethereum-attestation-service/eas-sdk": "2.6.0",
1818
"@ethereumjs/rlp": "^5.0.2",
1919
"@hookform/resolvers": "^3.9.0",
20+
"@lighthouse-web3/kavach": "^0.1.9",
21+
"@lighthouse-web3/sdk": "^0.3.7",
2022
"@noble/secp256k1": "^2.1.0",
2123
"@openzeppelin/merkle-tree": "^1.0.7",
2224
"@radix-ui/colors": "^3.0.0",
@@ -67,10 +69,12 @@
6769
"ethers": "^6.13.2",
6870
"fast-querystring": "^1.1.2",
6971
"fflate": "^0.8.2",
72+
"got": "^14.4.5",
7073
"graphology": "^0.25.4",
7174
"graphql-request": "^7.1.0",
7275
"jotai": "^2.10.0",
7376
"jsdom": "^25.0.1",
77+
"ky": "^1.7.2",
7478
"lodash": "^4.17.21",
7579
"lucide-react": "^0.446.0",
7680
"micro-eth-signer": "^0.12.0",
@@ -81,8 +85,10 @@
8185
"react-dom": "^18.3.1",
8286
"react-dropzone": "^14.2.9",
8387
"react-hook-form": "^7.53.0",
88+
"readable-stream": "^4.5.2",
8489
"sha256": "link:@noble/hashes/sha256",
8590
"sigma": "3.0.0-beta.31",
91+
"stream-browserify": "^3.0.0",
8692
"tailwind-merge": "^2.5.2",
8793
"tailwindcss": "^3.4.13",
8894
"tailwindcss-animate": "^1.0.7",

Diff for: apps/storybook/src/components/ui/toaster.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import {
88
} from "@/components/ui/toast";
99
import { useToast } from "@/hooks/use-toast";
1010

11-
export function Toaster() {
11+
export function Toaster({
12+
className,
13+
}: {
14+
className?: string;
15+
}) {
1216
const { toasts } = useToast();
1317

1418
return (
@@ -27,7 +31,7 @@ export function Toaster() {
2731
</Toast>
2832
);
2933
})}
30-
<ToastViewport />
34+
<ToastViewport className={className} />
3135
</ToastProvider>
3236
);
3337
}

Diff for: apps/storybook/src/lib/blockscout/chainInfo.ts

+27-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export const BLOCKSCOUT_CHAINS_BY_ID = {
1+
export const BLOCKSCOUT_CHAINS_BY_ID_IMPORTED = {
22
"1": {
33
name: "Ethereum",
44
description:
@@ -8943,3 +8943,29 @@ export const BLOCKSCOUT_CHAINS_BY_ID = {
89438943
],
89448944
},
89458945
} as Record<string, any>;
8946+
8947+
// Add Akave Fuji as not in chain Info yet
8948+
8949+
const BLOCKSCOUT_CHAINS_BY_ID_EXTENSION = {
8950+
"78963": {
8951+
name: "Akave Fuji",
8952+
description: "Akave Fuji",
8953+
logo: "",
8954+
ecosystem: "Ethereum",
8955+
isTestnet: false,
8956+
layer: 1,
8957+
rollupType: null,
8958+
website: "https://www.akave.ai/",
8959+
explorers: [
8960+
{
8961+
url: "http://explorer.akave.ai/",
8962+
hostedBy: "self",
8963+
},
8964+
],
8965+
},
8966+
};
8967+
8968+
export const BLOCKSCOUT_CHAINS_BY_ID = {
8969+
...BLOCKSCOUT_CHAINS_BY_ID_IMPORTED,
8970+
...BLOCKSCOUT_CHAINS_BY_ID_EXTENSION,
8971+
};
+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { faker } from "@faker-js/faker";
2+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
3+
import {
4+
AkaveBucket,
5+
createBucket,
6+
listBucketFiles,
7+
listBuckets,
8+
uploadFileObject,
9+
uploadFileWithFormData,
10+
} from "./client";
11+
12+
import fs from "fs";
13+
import os from "os";
14+
import path from "path";
15+
16+
describe(
17+
"with file",
18+
() => {
19+
const AKAVE_ENDPOINT_URL = import.meta.env.VITE_AKAVE_ENDPOINT_URL;
20+
const config = {
21+
akaveEndpointUrl: AKAVE_ENDPOINT_URL,
22+
};
23+
const testBucketNameCreate = "test-bucket-create";
24+
const testBucketNameExists = "test-bucket";
25+
26+
const tmpDir = os.tmpdir();
27+
const fileName = "test.txt";
28+
const filePath = path.join(tmpDir, fileName);
29+
30+
const testContent = faker.lorem.paragraphs(10);
31+
32+
beforeAll(() => {
33+
fs.writeFileSync(filePath, testContent, {
34+
encoding: "utf8",
35+
});
36+
});
37+
38+
test.skip("create bucket", async () => {
39+
const results = await createBucket({
40+
...config,
41+
bucketName: testBucketNameCreate,
42+
});
43+
44+
const { data, success } = results;
45+
46+
expect(success).toEqual(true);
47+
expect(data?.ID).toBeDefined();
48+
expect(data?.transactionHash).toBeDefined();
49+
});
50+
51+
test("#listBuckets", async () => {
52+
const results = await listBuckets(config);
53+
console.log("results", results);
54+
expect(
55+
!!results.data!.find(
56+
(bucket: any) => bucket.Name === testBucketNameExists,
57+
),
58+
).toEqual(true);
59+
});
60+
61+
test("#listBucketFiles", async () => {
62+
const files = await listBucketFiles({
63+
...config,
64+
bucketName: testBucketNameExists,
65+
});
66+
console.log("files", files);
67+
// expect(!!files.length > 0).toEqual(true);
68+
});
69+
70+
test("uploadFileObject", async () => {
71+
const file = {
72+
lorem: testContent,
73+
};
74+
const response = await uploadFileObject({
75+
...config,
76+
fileName,
77+
file,
78+
bucketName: testBucketNameExists,
79+
});
80+
expect(response).toHaveProperty("success", true);
81+
// Add more assertions as needed
82+
});
83+
84+
test.only("uploadFileWithFormData", async () => {
85+
const file = new File([testContent], fileName, {
86+
type: "text/plain",
87+
});
88+
const response = await uploadFileWithFormData({
89+
...config,
90+
fileName,
91+
file,
92+
bucketName: testBucketNameExists,
93+
});
94+
});
95+
96+
// TODO delete bucket
97+
afterAll(async () => {});
98+
},
99+
60 * 1000,
100+
);

Diff for: apps/storybook/src/lib/filecoin/akave/client.ts

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import ky from "ky";
2+
3+
export interface AkaveFile {
4+
Name: string;
5+
RootCID: string;
6+
Size: string;
7+
CreatedAt: string;
8+
}
9+
10+
export interface AkaveBucket {
11+
ID: string;
12+
Name: string;
13+
CreatedAt: string;
14+
files: File[];
15+
}
16+
17+
/**
18+
* Authentication and browser-compatability are to be confirmed
19+
* TODO refactor akave config object
20+
*/
21+
22+
const AKAVE_ENDPOINT_URL = process.env.AKAVE_ENDPOINT_URL || "localhost:3000";
23+
24+
export const getBucketMetadata = ({
25+
akaveEndpointUrl = AKAVE_ENDPOINT_URL,
26+
bucketName,
27+
}: {
28+
akaveEndpointUrl: string;
29+
bucketName: string;
30+
}) => {
31+
const url = `${akaveEndpointUrl}/buckets/${bucketName}`;
32+
33+
return ky(url).then((response) => response.json());
34+
};
35+
36+
export const listBucketFiles = ({
37+
akaveEndpointUrl = AKAVE_ENDPOINT_URL,
38+
bucketName,
39+
}: {
40+
akaveEndpointUrl: string;
41+
bucketName: string;
42+
}): Promise<AkaveFile[]> => {
43+
const url = `${akaveEndpointUrl}/buckets/${bucketName}/files`;
44+
45+
return ky(url)
46+
.then((response) => response.json<{ data: AkaveFile[] }>())
47+
.then(({ data }) => {
48+
return data || [];
49+
});
50+
};
51+
52+
type ListBucketRes = {
53+
data?: Omit<AkaveBucket, "files">[];
54+
success: boolean;
55+
};
56+
export const listBuckets = ({
57+
akaveEndpointUrl = AKAVE_ENDPOINT_URL,
58+
}: {
59+
akaveEndpointUrl: string;
60+
}) => {
61+
const url = `${akaveEndpointUrl}/buckets`;
62+
63+
return ky(url).then((response) => response.json<ListBucketRes>());
64+
};
65+
66+
type CreateBucketRes = {
67+
data?: {
68+
ID: string;
69+
transactionHash: string;
70+
};
71+
success: boolean;
72+
};
73+
74+
export const createBucket = ({
75+
akaveEndpointUrl = AKAVE_ENDPOINT_URL,
76+
bucketName,
77+
}: {
78+
akaveEndpointUrl: string;
79+
bucketName: string;
80+
}) => {
81+
const url = `${akaveEndpointUrl}/buckets`;
82+
83+
return ky
84+
.post(url, {
85+
json: { bucketName },
86+
})
87+
.then((response) => response.json<CreateBucketRes>());
88+
};
89+
90+
export const createDownloadUrl = ({
91+
akaveEndpointUrl = AKAVE_ENDPOINT_URL,
92+
bucketName,
93+
fileName,
94+
}: {
95+
akaveEndpointUrl: string;
96+
bucketName: string;
97+
fileName: string;
98+
}) => {
99+
return `${akaveEndpointUrl}/buckets/${bucketName}/files/${fileName}/download`;
100+
};
101+
102+
export type UploadParams = {
103+
akaveEndpointUrl: string;
104+
bucketName: string;
105+
fileName: string;
106+
file: File | Blob | Object;
107+
};
108+
109+
const createUploadEndpoint = ({
110+
akaveEndpointUrl = AKAVE_ENDPOINT_URL,
111+
bucketName,
112+
}: {
113+
akaveEndpointUrl: string;
114+
bucketName: string;
115+
}) => {
116+
return `${akaveEndpointUrl}/buckets/${bucketName}/files`;
117+
};
118+
119+
export const uploadFileObject = ({
120+
akaveEndpointUrl = AKAVE_ENDPOINT_URL,
121+
bucketName,
122+
fileName,
123+
file,
124+
}: UploadParams) => {
125+
const endpoint = createUploadEndpoint({
126+
akaveEndpointUrl,
127+
bucketName,
128+
});
129+
130+
const options = typeof file === "string" ? { body: file } : { json: file };
131+
132+
return ky.post(endpoint, options).then((res) => res.json());
133+
};
134+
135+
export const uploadFileWithFormData = async ({
136+
akaveEndpointUrl = AKAVE_ENDPOINT_URL,
137+
bucketName,
138+
fileName,
139+
file,
140+
}: UploadParams): Promise<AkaveFile> => {
141+
const endpoint = createUploadEndpoint({
142+
akaveEndpointUrl,
143+
bucketName,
144+
});
145+
const formData = new FormData();
146+
// @ts-ignore
147+
formData.append("file", file);
148+
149+
// Caused by: RequestContentLengthMismatchError: Request body length does not match content-length header
150+
return ky
151+
.post(endpoint, {
152+
body: formData,
153+
})
154+
.then((res) => res.json())
155+
.then((results: any) => {
156+
const { success, data } = results;
157+
if (success) {
158+
return data;
159+
}
160+
161+
throw new Error("Upload Failed");
162+
});
163+
};

Diff for: apps/storybook/src/lib/filecoin/akave/fixture.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
abc

Diff for: apps/storybook/src/lib/filecoin/gateway.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { getLighthouseGatewayUrl } from "./lighthouse/isomorphic";
2+
3+
export enum FilecoinGateway {
4+
Lighthouse = "lighthouse",
5+
Akave = "akave",
6+
}
7+
8+
export const getGatewayUrlWithCid = (
9+
cid: string,
10+
gateway: FilecoinGateway = FilecoinGateway.Lighthouse,
11+
) => {
12+
// TODO discuss on retrieveal, auth and optimization required
13+
// if (gateway === FilecoinGateway.Akave) {
14+
// return "";
15+
// }
16+
17+
return getLighthouseGatewayUrl(cid);
18+
};

0 commit comments

Comments
 (0)