@show-karma/karma-gap-sdk 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/config/keys.example.json +6 -0
  2. package/core/abi/EAS.json +1 -0
  3. package/core/abi/SchemaRegistry.json +1 -0
  4. package/core/class/Attestation.ts +402 -0
  5. package/core/class/Fetcher.ts +202 -0
  6. package/core/class/GAP.d.ts +3 -1
  7. package/core/class/GAP.js +5 -2
  8. package/core/class/GAP.ts +398 -0
  9. package/core/class/GapSchema.ts +90 -0
  10. package/core/class/Gelato/Gelato.ts +286 -0
  11. package/core/class/GraphQL/AxiosGQL.ts +29 -0
  12. package/core/class/GraphQL/EASClient.ts +34 -0
  13. package/core/class/GraphQL/GapEasClient.ts +845 -0
  14. package/core/class/GraphQL/index.ts +3 -0
  15. package/core/class/Schema.ts +609 -0
  16. package/core/class/SchemaError.ts +36 -0
  17. package/core/class/contract/GapContract.js +2 -2
  18. package/core/class/contract/GapContract.ts +353 -0
  19. package/core/class/entities/Community.ts +115 -0
  20. package/core/class/entities/Grant.ts +309 -0
  21. package/core/class/entities/MemberOf.ts +42 -0
  22. package/core/class/entities/Milestone.ts +269 -0
  23. package/core/class/entities/Project.ts +370 -0
  24. package/core/class/entities/index.ts +5 -0
  25. package/core/class/index.ts +10 -0
  26. package/core/class/karma-indexer/GapIndexerClient.ts +245 -0
  27. package/core/class/remote-storage/IpfsStorage.ts +51 -0
  28. package/core/class/remote-storage/RemoteStorage.ts +65 -0
  29. package/core/class/types/attestations.ts +158 -0
  30. package/core/consts.js +1 -1
  31. package/core/consts.ts +282 -0
  32. package/core/index.ts +7 -0
  33. package/core/scripts/deploy.ts +67 -0
  34. package/core/scripts/index.ts +1 -0
  35. package/core/types.ts +186 -0
  36. package/core/utils/gelato/index.ts +3 -0
  37. package/core/utils/gelato/send-gelato-txn.ts +114 -0
  38. package/core/utils/gelato/sponsor-handler.ts +77 -0
  39. package/core/utils/gelato/watch-gelato-txn.ts +67 -0
  40. package/core/utils/get-date.ts +3 -0
  41. package/core/utils/get-ipfs-data.ts +13 -0
  42. package/core/utils/get-web3-provider.ts +20 -0
  43. package/core/utils/gql-queries.ts +133 -0
  44. package/core/utils/index.ts +7 -0
  45. package/core/utils/map-filter.ts +21 -0
  46. package/core/utils/serialize-bigint.ts +7 -0
  47. package/core/utils/to-unix.ts +18 -0
  48. package/csv-upload/.gitkeep +0 -0
  49. package/csv-upload/example.csv +2 -0
  50. package/csv-upload/scripts/run.ts +193 -0
  51. package/docs/.gitkeep +0 -0
  52. package/docs/images/attestation-architecture.png +0 -0
  53. package/docs/images/dfd-get-projects.png +0 -0
  54. package/index.ts +1 -0
  55. package/package.json +1 -1
  56. package/readme.md +39 -34
  57. package/schemas/.gitkeep +0 -0
  58. package/schemas/GAP-schemas-1692135812877.json +33 -0
  59. package/test-file.ts +92 -0
  60. package/tsconfig.json +26 -0
  61. package/core/class/AttestationIPFS.d.ts +0 -7
  62. package/core/class/AttestationIPFS.js +0 -10
  63. package/core/class/GraphQL/Fetcher.d.ts +0 -132
  64. package/core/class/GraphQL/Fetcher.js +0 -7
  65. package/core/class/GraphQL/GAPFetcher.d.ts +0 -160
  66. package/core/class/GraphQL/GAPFetcher.js +0 -516
  67. package/core/class/IPFS/IPFS.d.ts +0 -13
  68. package/core/class/IPFS/IPFS.js +0 -24
  69. package/core/class/contract/MultiAttest.d.ts +0 -10
  70. package/core/class/contract/MultiAttest.js +0 -19
@@ -0,0 +1,370 @@
1
+ import { Attestation } from '../Attestation';
2
+ import {
3
+ Grantee,
4
+ MemberDetails,
5
+ ProjectDetails,
6
+ Tag,
7
+ } from '../types/attestations';
8
+ import {
9
+ Hex,
10
+ MultiAttestPayload,
11
+ SignerOrProvider,
12
+ TNetwork,
13
+ } from 'core/types';
14
+ import { GapSchema } from '../GapSchema';
15
+ import { AttestationError } from '../SchemaError';
16
+ import { mapFilter } from '../../utils';
17
+ import { Grant } from './Grant';
18
+ import { nullRef } from '../../consts';
19
+ import { MemberOf } from './MemberOf';
20
+ import { GapContract } from '../contract/GapContract';
21
+
22
+ interface _Project extends Project {}
23
+
24
+ export interface IProject {
25
+ project: true;
26
+ }
27
+
28
+ export class Project extends Attestation<IProject> {
29
+ details?: ProjectDetails;
30
+ members: MemberOf[] = [];
31
+ grants: Grant[] = [];
32
+ grantee: Grantee;
33
+
34
+ /**
35
+ * Creates the payload for a multi-attestation.
36
+ *
37
+ * > if Current payload is set, it'll be used as the base payload
38
+ * and the project should refer to an index of the current payload,
39
+ * usually the community position.
40
+ *
41
+ * @param payload
42
+ * @param communityIdx
43
+ */
44
+ async multiAttestPayload(
45
+ currentPayload: MultiAttestPayload = [],
46
+ communityIdx = 0
47
+ ): Promise<MultiAttestPayload> {
48
+ const payload = [...currentPayload];
49
+ const projectIdx =
50
+ payload.push([this, await this.payloadFor(communityIdx)]) - 1;
51
+
52
+ if (this.details) {
53
+ payload.push([this.details, await this.details.payloadFor(projectIdx)]);
54
+ }
55
+
56
+ if (this.members?.length) {
57
+ await Promise.all(
58
+ this.members.map(async (m) =>
59
+ payload.push(...(await m.multiAttestPayload(payload, projectIdx)))
60
+ )
61
+ );
62
+ }
63
+
64
+ if (this.grants?.length) {
65
+ await Promise.all(
66
+ this.grants.map(async (g) =>
67
+ payload.push(...(await g.multiAttestPayload(payload, projectIdx)))
68
+ )
69
+ );
70
+ }
71
+
72
+ return payload.slice(currentPayload.length, payload.length);
73
+ }
74
+
75
+ async attest(signer: SignerOrProvider): Promise<void> {
76
+ const payload = await this.multiAttestPayload();
77
+ const uids = await GapContract.multiAttest(
78
+ signer,
79
+ payload.map((p) => p[1])
80
+ );
81
+
82
+ uids.forEach((uid, index) => {
83
+ payload[index][0].uid = uid;
84
+ });
85
+ }
86
+
87
+ async transferOwnership(signer: SignerOrProvider, newOwner: Hex) {
88
+ await GapContract.transferProjectOwnership(signer, this.uid, newOwner);
89
+ }
90
+
91
+ isOwner(signer: SignerOrProvider): Promise<boolean> {
92
+ return GapContract.isProjectOwner(signer, this.uid, this.chainID);
93
+ }
94
+
95
+ /**
96
+ * Add new members to the project.
97
+ * If any member in the array already exists in the project
98
+ * it'll be ignored.
99
+ * @param members
100
+ */
101
+ pushMembers(...members: Hex[]) {
102
+ this.members.push(
103
+ ...mapFilter(
104
+ members,
105
+ (member) => !!this.members.find((m) => m.recipient === member),
106
+ (member) =>
107
+ new MemberOf({
108
+ data: { memberOf: true },
109
+ refUID: this.uid,
110
+ schema: this.schema.gap.findSchema('MemberOf'),
111
+ recipient: member,
112
+ uid: nullRef,
113
+ })
114
+ )
115
+ );
116
+ }
117
+
118
+ /**
119
+ * Add new members to the project.
120
+ * If any member in the array already exists in the project
121
+ * it'll be ignored.
122
+ *
123
+ * __To modify member details, use `addMemberDetails(signer, MemberDetails[])` instead.__
124
+ * @param signer
125
+ * @param members
126
+ */
127
+ async attestMembers(signer: SignerOrProvider, members: MemberDetails[]) {
128
+ const newMembers = mapFilter(
129
+ members,
130
+ (member) => !this.members.find((m) => m.recipient === member.recipient),
131
+ // (member) => !!member,
132
+ (details) => {
133
+ const member = new MemberOf({
134
+ data: { memberOf: true },
135
+ refUID: this.uid,
136
+ schema: this.schema.gap.findSchema('MemberOf'),
137
+ createdAt: Date.now(),
138
+ recipient: details.recipient,
139
+ uid: nullRef,
140
+ });
141
+ return { member, details };
142
+ }
143
+ );
144
+
145
+ if (!newMembers.length) {
146
+ throw new AttestationError('ATTEST_ERROR', 'No new members to add.');
147
+ }
148
+
149
+ console.log(`Creating ${newMembers.length} new members`);
150
+
151
+ const attestedMembers = <Hex[]>await this.schema.multiAttest(
152
+ signer,
153
+ newMembers.map((m) => m.member)
154
+ );
155
+
156
+ console.log('attested-members', attestedMembers);
157
+
158
+ newMembers.forEach(({ member, details }, idx) => {
159
+ Object.assign(member, { uid: attestedMembers[idx] });
160
+
161
+ if (!details) return;
162
+ Object.assign(details, { refUID: attestedMembers[idx] });
163
+ });
164
+
165
+ this.members.push(...newMembers.map((m) => m.member));
166
+
167
+ await this.addMemberDetails(
168
+ signer,
169
+ newMembers.map((m) => m.details)
170
+ );
171
+ }
172
+
173
+ /**
174
+ * Add new details to the members of a project. Note that it will overwrite
175
+ * any existing details.
176
+ *
177
+ * @param signer
178
+ * @param entities
179
+ */
180
+ private async addMemberDetails(
181
+ signer: SignerOrProvider,
182
+ entities: MemberDetails[]
183
+ ) {
184
+ // Check if any of members should be revoked (details modified)
185
+ const toRevoke = mapFilter(
186
+ this.members,
187
+ (member) =>
188
+ !!entities.find(
189
+ (entity) =>
190
+ member.uid === entity.refUID &&
191
+ member.details &&
192
+ member.details?.refUID !== entity.refUID
193
+ ),
194
+ (member) => member.uid
195
+ );
196
+
197
+ if (toRevoke.length) {
198
+ console.log('Revoking details');
199
+ await this.cleanDetails(signer, toRevoke);
200
+ }
201
+
202
+ console.log(`Creating ${entities.length} new member details`);
203
+
204
+ const attestedEntities = <Hex[]>(
205
+ await this.schema.multiAttest(signer, entities)
206
+ );
207
+ console.log('attested-entities', attestedEntities);
208
+
209
+ entities.forEach((entity, idx) => {
210
+ const member = this.members.find(
211
+ (member) => member.uid === entity.refUID
212
+ );
213
+ if (!member) return;
214
+
215
+ Object.assign(entity, { uid: attestedEntities[idx] });
216
+ member.details = entity;
217
+ });
218
+ }
219
+
220
+ /**
221
+ * Clean member details.
222
+ * @param signer
223
+ * @param uids
224
+ */
225
+ async cleanDetails(signer: SignerOrProvider, uids: Hex[]) {
226
+ if (!uids.length) {
227
+ throw new AttestationError('ATTEST_ERROR', 'No details to clean.');
228
+ }
229
+ const memberDetails = this.schema.gap.findSchema('MemberDetails');
230
+
231
+ await this.schema.multiRevoke(
232
+ signer,
233
+ uids.map((uid) => ({ schemaId: memberDetails.uid, uid }))
234
+ );
235
+
236
+ this.members.forEach((member) => {
237
+ if (!member.details) return;
238
+ if (uids.includes(member.details.uid)) {
239
+ member.details = undefined;
240
+ }
241
+ });
242
+ }
243
+
244
+ /**
245
+ * Remove members from the project.
246
+ * @param signer
247
+ * @param uids
248
+ * @returns
249
+ */
250
+ async removeMembers(signer: SignerOrProvider, uids: Hex[]) {
251
+ if (!uids.length) {
252
+ throw new AttestationError('ATTEST_ERROR', 'No members to remove.');
253
+ }
254
+ const memberOf = this.schema.gap.findSchema('MemberOf');
255
+
256
+ const details = mapFilter(
257
+ this.members,
258
+ (m) => uids.includes(m.uid) && !!m.details,
259
+ (m) => m.details?.uid
260
+ );
261
+
262
+ if (details.length) {
263
+ await this.cleanDetails(signer, details);
264
+ }
265
+
266
+ await this.schema.multiRevoke(
267
+ signer,
268
+ uids.map((uid) => ({ schemaId: memberOf.uid, uid }))
269
+ );
270
+
271
+ this.members = this.members.filter((m) => !uids.includes(m.uid));
272
+ }
273
+
274
+ /**
275
+ * Remove all members from the project.
276
+ * @param signer
277
+ */
278
+ async removeAllMembers(signer: SignerOrProvider) {
279
+ const members = mapFilter(
280
+ this.members,
281
+ (m) => !!m.uid,
282
+ (m) => m.uid
283
+ );
284
+
285
+ if (!members.length) {
286
+ throw new AttestationError('REVOKATION_ERROR', 'No members to revoke.');
287
+ }
288
+
289
+ const details = mapFilter(
290
+ this.members,
291
+ (m) => !!m.details,
292
+ (m) => m.details?.uid
293
+ );
294
+ if (details.length) {
295
+ await this.cleanDetails(signer, details);
296
+ }
297
+
298
+ await this.removeMembers(signer, members);
299
+ this.members.splice(0, this.members.length);
300
+ }
301
+
302
+ static from(attestations: _Project[], network: TNetwork): Project[] {
303
+ return attestations.map((attestation) => {
304
+ const project = new Project({
305
+ ...attestation,
306
+ data: {
307
+ project: true,
308
+ },
309
+ schema: GapSchema.find('Project', network),
310
+ chainID: attestation.chainID,
311
+ });
312
+
313
+ if (attestation.details) {
314
+ const { details } = attestation;
315
+ project.details = new ProjectDetails({
316
+ ...details,
317
+ data: {
318
+ ...details.data,
319
+ },
320
+ schema: GapSchema.find('ProjectDetails', network),
321
+ chainID: attestation.chainID,
322
+ });
323
+
324
+ project.details.links = details.data.links || [];
325
+ project.details.tags = details.data.tags || [];
326
+
327
+ if ((attestation.data as any).links) {
328
+ project.details.links = (attestation.data as any).links;
329
+ }
330
+
331
+ if ((attestation.data as any).tags) {
332
+ project.details.tags = (attestation as any).tags;
333
+ }
334
+ }
335
+
336
+ if (attestation.members) {
337
+ project.members = attestation.members.map((m) => {
338
+ const member = new MemberOf({
339
+ ...m,
340
+ data: {
341
+ memberOf: true,
342
+ },
343
+ schema: GapSchema.find('MemberOf', network),
344
+ chainID: attestation.chainID,
345
+ });
346
+
347
+ if (m.details) {
348
+ const { details } = m;
349
+ member.details = new MemberDetails({
350
+ ...details,
351
+ data: {
352
+ ...details.data,
353
+ },
354
+ schema: GapSchema.find('MemberDetails', network),
355
+ chainID: attestation.chainID,
356
+ });
357
+ }
358
+
359
+ return member;
360
+ });
361
+ }
362
+
363
+ if (attestation.grants) {
364
+ project.grants = Grant.from(attestation.grants, network);
365
+ }
366
+
367
+ return project;
368
+ });
369
+ }
370
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./Grant";
2
+ export * from "./Milestone";
3
+ export * from "./Project";
4
+ export * from "./Community";
5
+ export * from "./MemberOf";
@@ -0,0 +1,10 @@
1
+ export * from './Attestation';
2
+ export * from './GAP';
3
+ export * from './GapSchema';
4
+ export * from './Schema';
5
+ export * from './SchemaError';
6
+ export * from './entities';
7
+ export * from './Fetcher';
8
+ export * from './karma-indexer/GapIndexerClient';
9
+ export * from './remote-storage/IpfsStorage';
10
+ export * from './remote-storage/RemoteStorage';
@@ -0,0 +1,245 @@
1
+ import { TSchemaName, IAttestation, TNetwork, Hex } from 'core/types';
2
+ import { Attestation } from '../Attestation';
3
+ import { GapSchema } from '../GapSchema';
4
+ import { Fetcher } from '../Fetcher';
5
+ import { Community, Project, Grant, Milestone, MemberOf } from '../entities';
6
+ import { Grantee } from '../types/attestations';
7
+
8
+ const Endpoints = {
9
+ attestations: {
10
+ all: () => '/attestations',
11
+ byUid: (uid: Hex) => `/attestations/${uid}`,
12
+ },
13
+ communities: {
14
+ all: () => '/communities',
15
+ byUidOrSlug: (uidOrSlug: string) => `/communities/${uidOrSlug}`,
16
+ grants: (uidOrSlug: string) => `/communities/${uidOrSlug}/grants`,
17
+ },
18
+ grantees: {
19
+ all: () => '/grantees',
20
+ byAddress: (address: Hex) => `/grantees/${address}`,
21
+ grants: (address: Hex) => `/grantees/${address}/grants`,
22
+ projects: (address: Hex) => `/grantees/${address}/projects`,
23
+ communities: (address: Hex, withGrants) =>
24
+ `/grantees/${address}/communities${withGrants ? '?withGrants=true' : ''}`,
25
+ },
26
+ grants: {
27
+ all: () => '/grants',
28
+ byUid: (uid: Hex) => `/grants/${uid}`,
29
+ byExternalId: (id: string) => `/grants/external-id/${id}`,
30
+ },
31
+ project: {
32
+ all: () => '/projects',
33
+ byUidOrSlug: (uidOrSlug: string) => `/projects/${uidOrSlug}`,
34
+ grants: (uidOrSlug: string) => `/projects/${uidOrSlug}/grants`,
35
+ milestones: (uidOrSlug: string) => `/projects/${uidOrSlug}/milestones`,
36
+ },
37
+ };
38
+
39
+ export class GapIndexerClient extends Fetcher {
40
+ async attestation<T = unknown>(
41
+ uid: `0x${string}`
42
+ ): Promise<Attestation<T, GapSchema>> {
43
+ const { data } = await this.client.get<IAttestation>(
44
+ Endpoints.attestations.byUid(uid)
45
+ );
46
+
47
+ if (!data) throw new Error('Attestation not found');
48
+ return Attestation.fromInterface<Attestation<T>>(
49
+ [data],
50
+ this.gap.network
51
+ )[0];
52
+ }
53
+
54
+ async attestations(
55
+ schemaName: TSchemaName,
56
+ search?: string
57
+ ): Promise<IAttestation[]> {
58
+ const { data } = await this.client.get<IAttestation[]>(
59
+ Endpoints.attestations.all(),
60
+ {
61
+ params: {
62
+ 'filter[schemaUID]': this.gap.findSchema(schemaName).uid,
63
+ 'filter[data]': search,
64
+ },
65
+ }
66
+ );
67
+
68
+ return data || [];
69
+ }
70
+
71
+ async attestationsOf(
72
+ schemaName: TSchemaName,
73
+ attester: `0x${string}`
74
+ ): Promise<IAttestation[]> {
75
+ const { data } = await this.client.get<IAttestation[]>(
76
+ Endpoints.attestations.all(),
77
+ {
78
+ params: {
79
+ 'filter[schemaUID]': this.gap.findSchema(schemaName).uid,
80
+ 'filter[recipient]': attester,
81
+ },
82
+ }
83
+ );
84
+
85
+ return data || [];
86
+ }
87
+
88
+ attestationsTo(
89
+ schemaName: TSchemaName,
90
+ recipient: `0x${string}`
91
+ ): Promise<IAttestation[]> {
92
+ return this.attestationsOf(schemaName, recipient);
93
+ }
94
+
95
+ async communities(search?: string): Promise<Community[]> {
96
+ const { data } = await this.client.get<Community[]>(
97
+ Endpoints.communities.all(),
98
+ {
99
+ params: {
100
+ 'filter[name]': search,
101
+ },
102
+ }
103
+ );
104
+
105
+ return Community.from(data, this.gap.network);
106
+ }
107
+
108
+ async communitiesOf(address: Hex, withGrants: boolean): Promise<Community[]> {
109
+ const { data } = await this.client.get<Community[]>(
110
+ Endpoints.grantees.communities(address, withGrants)
111
+ );
112
+
113
+ return Community.from(data, this.gap.network);
114
+ }
115
+
116
+ communitiesByIds(uids: `0x${string}`[]): Promise<Community[]> {
117
+ throw new Error('Method not implemented.');
118
+ }
119
+
120
+ async communityBySlug(slug: string): Promise<Community> {
121
+ const { data } = await this.client.get<Community>(
122
+ Endpoints.communities.byUidOrSlug(slug)
123
+ );
124
+
125
+ return Community.from([data], this.gap.network)[0];
126
+ }
127
+
128
+ communityById(uid: `0x${string}`): Promise<Community> {
129
+ return this.communityBySlug(uid);
130
+ }
131
+
132
+ async projectBySlug(slug: string): Promise<Project> {
133
+ const { data } = await this.client.get<Project>(
134
+ Endpoints.project.byUidOrSlug(slug)
135
+ );
136
+
137
+ return Project.from([data], this.gap.network)[0];
138
+ }
139
+
140
+ projectById(uid: `0x${string}`): Promise<Project> {
141
+ return this.projectBySlug(uid);
142
+ }
143
+
144
+ async searchProjects(query: string): Promise<Project[]> {
145
+ const { data } = await this.client.get<Project[]>(Endpoints.project.all(), {
146
+ params: {
147
+ q: query,
148
+ },
149
+ });
150
+
151
+ return Project.from(data, this.gap.network);
152
+ }
153
+
154
+ async projects(name?: string): Promise<Project[]> {
155
+ const { data } = await this.client.get<Project[]>(Endpoints.project.all(), {
156
+ params: {
157
+ 'filter[title]': name,
158
+ },
159
+ });
160
+
161
+ return Project.from(data, this.gap.network);
162
+ }
163
+
164
+ async projectsOf(grantee: `0x${string}`): Promise<Project[]> {
165
+ const { data } = await this.client.get<Project[]>(
166
+ Endpoints.grantees.projects(grantee)
167
+ );
168
+
169
+ return Project.from(data, this.gap.network);
170
+ }
171
+
172
+ async grantee(address: `0x${string}`): Promise<Grantee> {
173
+ const { data } = await this.client.get<Grantee>(
174
+ Endpoints.grantees.byAddress(address)
175
+ );
176
+
177
+ return data;
178
+ }
179
+
180
+ async grantees(): Promise<Grantee[]> {
181
+ const { data } = await this.client.get<Grantee[]>(Endpoints.grantees.all());
182
+
183
+ return data;
184
+ }
185
+
186
+ async grantsOf(
187
+ grantee: `0x${string}`,
188
+ withCommunity?: boolean
189
+ ): Promise<Grant[]> {
190
+ const { data } = await this.client.get<Grant[]>(
191
+ Endpoints.grantees.grants(grantee)
192
+ );
193
+
194
+ return Grant.from(data, this.gap.network);
195
+ }
196
+
197
+ async grantsFor(
198
+ projects: Project[],
199
+ withCommunity?: boolean
200
+ ): Promise<Grant[]> {
201
+ const { data } = await this.client.get<Grant[]>(
202
+ Endpoints.project.grants(projects[0].uid)
203
+ );
204
+
205
+ return Grant.from(data, this.gap.network);
206
+ }
207
+
208
+ async grantsForExtProject(projectExtId: string): Promise<Grant[]> {
209
+ const { data } = await this.client.get<Grant[]>(
210
+ Endpoints.grants.byExternalId(projectExtId)
211
+ );
212
+
213
+ return Grant.from(data, this.gap.network);
214
+ }
215
+
216
+ async grantsByCommunity(uid: `0x${string}`) {
217
+ const { data } = await this.client.get<Grant[]>(
218
+ Endpoints.communities.grants(uid)
219
+ );
220
+
221
+ return Grant.from(data, this.gap.network);
222
+ }
223
+
224
+ async milestonesOf(grants: Grant[]): Promise<Milestone[]> {
225
+ const { data } = await this.client.get<Milestone[]>(
226
+ Endpoints.project.milestones(grants[0].uid)
227
+ );
228
+
229
+ return Milestone.from(data, this.gap.network);
230
+ }
231
+
232
+ async membersOf(projects: Project[]): Promise<MemberOf[]> {
233
+ throw new Error('Method not implemented.');
234
+ }
235
+
236
+ async slugExists(slug: string): Promise<boolean> {
237
+ try {
238
+ await this.client.get<Project>(Endpoints.project.byUidOrSlug(slug));
239
+
240
+ return true;
241
+ } catch {
242
+ return false;
243
+ }
244
+ }
245
+ }
@@ -0,0 +1,51 @@
1
+ import { NFTStorage } from 'nft.storage';
2
+ import { RemoteStorage } from './RemoteStorage';
3
+ import { RemoteStorageError } from '../SchemaError';
4
+ import { getIPFSData } from '../../utils';
5
+ import { STORAGE_TYPE, TRemoteStorageOutput } from 'core/types';
6
+
7
+ export interface IpfsStorageOptions {
8
+ token: string;
9
+ endpoint?: URL;
10
+ }
11
+
12
+ export class IpfsStorage extends RemoteStorage<NFTStorage> {
13
+ constructor(
14
+ opts: IpfsStorageOptions,
15
+ /**
16
+ * If set, will send request to another server instead of
17
+ * using the local instance
18
+ */
19
+ sponsor?: RemoteStorage['sponsor']
20
+ ) {
21
+ super(STORAGE_TYPE.IPFS, sponsor);
22
+
23
+ this.assert(opts);
24
+ this.client = new NFTStorage({ ...opts });
25
+ }
26
+
27
+ private assert(opts: IpfsStorageOptions) {}
28
+
29
+ async save<T = unknown>(data: T): Promise<string> {
30
+ try {
31
+ const blob = new Blob([JSON.stringify(data)], {
32
+ type: 'application/json',
33
+ });
34
+ const cid = await this.client.storeBlob(blob);
35
+ return cid;
36
+ } catch (error) {
37
+ throw new RemoteStorageError(
38
+ 'REMOTE_STORAGE_UPLOAD',
39
+ `Error adding data to IPFS`
40
+ );
41
+ }
42
+ }
43
+
44
+ encode(data: string): TRemoteStorageOutput<string> {
45
+ return { hash: data, storageType: this.storageType };
46
+ }
47
+
48
+ async get<T = unknown>(args: { cid: string }): Promise<T> {
49
+ return getIPFSData<T>(args.cid);
50
+ }
51
+ }