@show-karma/karma-gap-sdk 0.3.3 → 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 (69) 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.ts +282 -0
  31. package/core/index.ts +7 -0
  32. package/core/scripts/deploy.ts +67 -0
  33. package/core/scripts/index.ts +1 -0
  34. package/core/types.ts +186 -0
  35. package/core/utils/gelato/index.ts +3 -0
  36. package/core/utils/gelato/send-gelato-txn.ts +114 -0
  37. package/core/utils/gelato/sponsor-handler.ts +77 -0
  38. package/core/utils/gelato/watch-gelato-txn.ts +67 -0
  39. package/core/utils/get-date.ts +3 -0
  40. package/core/utils/get-ipfs-data.ts +13 -0
  41. package/core/utils/get-web3-provider.ts +20 -0
  42. package/core/utils/gql-queries.ts +133 -0
  43. package/core/utils/index.ts +7 -0
  44. package/core/utils/map-filter.ts +21 -0
  45. package/core/utils/serialize-bigint.ts +7 -0
  46. package/core/utils/to-unix.ts +18 -0
  47. package/csv-upload/.gitkeep +0 -0
  48. package/csv-upload/example.csv +2 -0
  49. package/csv-upload/scripts/run.ts +193 -0
  50. package/docs/.gitkeep +0 -0
  51. package/docs/images/attestation-architecture.png +0 -0
  52. package/docs/images/dfd-get-projects.png +0 -0
  53. package/index.ts +1 -0
  54. package/package.json +1 -1
  55. package/readme.md +39 -34
  56. package/schemas/.gitkeep +0 -0
  57. package/schemas/GAP-schemas-1692135812877.json +33 -0
  58. package/test-file.ts +92 -0
  59. package/tsconfig.json +26 -0
  60. package/core/class/AttestationIPFS.d.ts +0 -7
  61. package/core/class/AttestationIPFS.js +0 -10
  62. package/core/class/GraphQL/Fetcher.d.ts +0 -132
  63. package/core/class/GraphQL/Fetcher.js +0 -7
  64. package/core/class/GraphQL/GAPFetcher.d.ts +0 -160
  65. package/core/class/GraphQL/GAPFetcher.js +0 -516
  66. package/core/class/IPFS/IPFS.d.ts +0 -13
  67. package/core/class/IPFS/IPFS.js +0 -24
  68. package/core/class/contract/MultiAttest.d.ts +0 -10
  69. package/core/class/contract/MultiAttest.js +0 -19
@@ -0,0 +1,845 @@
1
+ import { Attestation } from '../Attestation';
2
+ import { gqlQueries } from '../../utils/gql-queries';
3
+ import { toUnix } from '../../utils/to-unix';
4
+ import {
5
+ AttestationRes,
6
+ AttestationsRes,
7
+ EASNetworkConfig,
8
+ Hex,
9
+ IAttestation,
10
+ SchemaRes,
11
+ SchemataRes,
12
+ TNetwork,
13
+ TSchemaName,
14
+ } from '../../types';
15
+ import {
16
+ CommunityDetails,
17
+ GrantDetails,
18
+ GrantUpdate,
19
+ Grantee,
20
+ MemberDetails,
21
+ MilestoneCompleted,
22
+ ProjectDetails,
23
+ } from '../types/attestations';
24
+ import { GapSchema } from '../GapSchema';
25
+ import { Schema } from '../Schema';
26
+ import { SchemaError } from '../SchemaError';
27
+ import { Grant, Milestone, IProject, Project, MemberOf } from '../entities';
28
+ import { Community } from '../entities/Community';
29
+ import { mapFilter } from '../../utils';
30
+ import { Fetcher } from '../Fetcher';
31
+ import { Networks } from '../../consts';
32
+
33
+ interface EASClientProps {
34
+ network: TNetwork;
35
+ }
36
+
37
+ // TODO: Split this class into small ones
38
+ export class GapEasClient extends Fetcher {
39
+
40
+ network: EASNetworkConfig & { name: TNetwork };
41
+
42
+ constructor(args: EASClientProps) {
43
+ const { network } = args;
44
+ super(Networks[network].url);
45
+
46
+ this.network = { ...Networks[network], name: network };
47
+ }
48
+
49
+ /**
50
+ * Fetches all the schemas deployed by an owner
51
+ * @param owner
52
+ */
53
+ async schemas(owner: Hex): Promise<GapSchema[]> {
54
+ const query = gqlQueries.schemata(owner);
55
+ const { schemata } = await this.query<SchemataRes>(query);
56
+
57
+ return schemata.map(
58
+ (schema) =>
59
+ new GapSchema(
60
+ {
61
+ name: '',
62
+ schema: Schema.rawToObject(schema.schema),
63
+ uid: schema.uid,
64
+ },
65
+ this.gap
66
+ )
67
+ );
68
+ }
69
+
70
+ async attestation<T = unknown>(uid: Hex) {
71
+ const query = gqlQueries.attestation(uid);
72
+ const { attestation } = await this.query<AttestationRes>(query);
73
+
74
+ return Attestation.fromInterface<Attestation<T>>(
75
+ [attestation],
76
+ this.network.name
77
+ )[0];
78
+ }
79
+
80
+ async attestations(
81
+ schemaName: TSchemaName,
82
+ search?: string
83
+ ): Promise<IAttestation[]> {
84
+ const schema = this.gap.findSchema(schemaName);
85
+
86
+ const query = gqlQueries.attestationsOf(schema.uid, search);
87
+ const {
88
+ schema: { attestations },
89
+ } = await this.query<SchemaRes>(query);
90
+
91
+ return attestations;
92
+ }
93
+
94
+ async attestationsOf(
95
+ schemaName: TSchemaName,
96
+ recipient: Hex
97
+ ): Promise<IAttestation[]> {
98
+ const schema = this.gap.findSchema(schemaName);
99
+ const query = gqlQueries.attestationsOf(schema.uid, recipient);
100
+ const {
101
+ schema: { attestations },
102
+ } = await this.query<SchemaRes>(query);
103
+
104
+ return attestations;
105
+ }
106
+
107
+ async attestationsTo(
108
+ schemaName: TSchemaName,
109
+ recipient: Hex
110
+ ): Promise<IAttestation[]> {
111
+ const schema = this.gap.findSchema(schemaName);
112
+ const query = gqlQueries.attestationsTo(schema.uid, recipient);
113
+ const {
114
+ schema: { attestations },
115
+ } = await this.query<SchemaRes>(query);
116
+
117
+ return attestations;
118
+ }
119
+
120
+ /**
121
+ * Fetch all dependent attestations of a parent schema.
122
+ * @param parentSchema the schema name to get dependents of.
123
+ * @param parentUid the parent uid to get dependents of.
124
+ */
125
+ async dependentsOf(
126
+ parentSchema: TSchemaName,
127
+ parentUid: Hex
128
+ ): Promise<Attestation[]> {
129
+ const parent = this.gap.findSchema(parentSchema);
130
+ const children = parent.children.map((c) => c.uid);
131
+
132
+ if (!children.length)
133
+ throw new SchemaError(
134
+ 'INVALID_REFERENCE',
135
+ `Schema ${parentSchema} has no children.`
136
+ );
137
+
138
+ const query = gqlQueries.dependentsOf(parentUid, children);
139
+ const { attestations } = await this.query<AttestationsRes>(query);
140
+
141
+ return Attestation.fromInterface(attestations, this.network.name);
142
+ }
143
+
144
+ async communities(search?: string) {
145
+ const [community, communityDetails] = this.gap.findManySchemas([
146
+ 'Community',
147
+ 'CommunityDetails',
148
+ ]);
149
+
150
+ const query = gqlQueries.attestationsOf(community.uid, search);
151
+ const {
152
+ schema: { attestations },
153
+ } = await this.query<SchemaRes>(query);
154
+
155
+ const communities = Attestation.fromInterface<Community>(
156
+ attestations,
157
+ this.network.name
158
+ );
159
+
160
+ if (!communities.length) return [];
161
+
162
+ return this.communitiesDetails(communities);
163
+ }
164
+
165
+ async communitiesOf(address?: Hex) {
166
+ const [community] = this.gap.findManySchemas(['Community']);
167
+
168
+ const query = gqlQueries.attestationsTo(community.uid, address);
169
+ const {
170
+ schema: { attestations },
171
+ } = await this.query<SchemaRes>(query);
172
+
173
+ const communities = Attestation.fromInterface<Community>(
174
+ attestations,
175
+ this.network.name
176
+ );
177
+
178
+ if (!communities.length) return [];
179
+
180
+ return this.communitiesDetails(communities);
181
+ }
182
+
183
+ async communitiesByIds(uids: Hex[]) {
184
+ if (!uids.length) return [];
185
+ const communityDetails = this.gap.findSchema('CommunityDetails');
186
+
187
+ const communityQuery = gqlQueries.attestationsIn(uids);
188
+ const detailsQuery = gqlQueries.dependentsOf(uids, [communityDetails.uid]);
189
+ try {
190
+ const [communities, details] = await Promise.all([
191
+ this.query<AttestationsRes>(communityQuery),
192
+ this.query<AttestationsRes>(detailsQuery),
193
+ ]);
194
+
195
+ const communitiesAttestations = Attestation.fromInterface<Community>(
196
+ communities.attestations || [],
197
+ this.network.name
198
+ );
199
+
200
+ const detailsAttestations = Attestation.fromInterface<CommunityDetails>(
201
+ details.attestations || [],
202
+ this.network.name
203
+ );
204
+
205
+ communitiesAttestations.forEach((community) => {
206
+ community.details = detailsAttestations.find(
207
+ (d) => d.refUID === community.uid
208
+ );
209
+ });
210
+
211
+ return communitiesAttestations;
212
+ } catch (error) {
213
+ throw error;
214
+ }
215
+ }
216
+
217
+ async communitiesDetails(communities: Community[]) {
218
+ const [project, communityDetails] = this.gap.findManySchemas([
219
+ 'Project',
220
+ 'CommunityDetails',
221
+ ]);
222
+
223
+ const ref = gqlQueries.dependentsOf(
224
+ communities.map((c) => c.uid),
225
+ [communityDetails.uid]
226
+ );
227
+
228
+ const results = await this.query<AttestationsRes>(ref);
229
+ const deps = Attestation.fromInterface(
230
+ results.attestations || [],
231
+ this.network.name
232
+ );
233
+
234
+ return communities.map((community) => {
235
+ community.projects = <Project[]>(
236
+ deps.filter(
237
+ (ref) =>
238
+ ref.schema.uid === project.uid && ref.refUID === community.uid
239
+ )
240
+ );
241
+
242
+ community.details = <CommunityDetails>(
243
+ deps.find(
244
+ (ref) =>
245
+ ref.schema.uid === communityDetails.uid &&
246
+ ref.refUID === community.uid
247
+ )
248
+ );
249
+
250
+ return community;
251
+ });
252
+ }
253
+
254
+ async communityBySlug(slug: string) {
255
+ const communitySchema = this.gap.findSchema('CommunityDetails');
256
+
257
+ const query = gqlQueries.attestationsOf(
258
+ communitySchema.uid,
259
+ this.getSearchFieldString('slug', slug)
260
+ );
261
+
262
+ try {
263
+ const {
264
+ schema: { attestations },
265
+ } = await this.query<SchemaRes>(query);
266
+
267
+ if (!attestations.length) throw new Error('Community not found.');
268
+
269
+ const communities = mapFilter(
270
+ Attestation.fromInterface<CommunityDetails>(
271
+ attestations,
272
+ this.network.name
273
+ ),
274
+ (details) => !!details.name,
275
+ (details) => {
276
+ const community = new Community({
277
+ data: { community: true },
278
+ uid: details.refUID,
279
+ schema: communitySchema,
280
+ recipient: details.recipient,
281
+ });
282
+
283
+ community.details = details;
284
+ return community;
285
+ }
286
+ );
287
+
288
+ const [withDetails] = await this.communitiesDetails(communities);
289
+
290
+ if (!withDetails) throw new Error('Community not found.');
291
+
292
+ const grants = await this.grantsByCommunity(withDetails.uid);
293
+ withDetails.grants = grants;
294
+
295
+ return withDetails;
296
+ } catch (error) {
297
+ throw error;
298
+ }
299
+ }
300
+
301
+ async communityById(uid: Hex) {
302
+ const query = gqlQueries.attestation(uid);
303
+ const { attestation } = await this.query<AttestationRes>(query);
304
+
305
+ if (!attestation) throw new Error('Community not found.');
306
+
307
+ const communities = Attestation.fromInterface<Community>(
308
+ [attestation],
309
+ this.network.name
310
+ ).map((c) => new Community(c));
311
+
312
+ const [withDetails] = await this.communitiesDetails(communities);
313
+ if (!withDetails) throw new Error('Community not found.');
314
+ const grants = await this.grantsByCommunity(uid);
315
+ withDetails.grants = grants;
316
+ return withDetails;
317
+ }
318
+
319
+ /**
320
+ * Fetch the details for a set of
321
+ * projects with project grants,
322
+ * members, milestones, and tags.
323
+ * @param projects
324
+ */
325
+ async projectsDetails(projects: Project[]) {
326
+ // Get projects array and fetch details, members, grants, etc then append to the project and return the array.
327
+
328
+ const [projectDetails] = this.gap.findManySchemas(['ProjectDetails']);
329
+
330
+ const refQuery = gqlQueries.dependentsOf(
331
+ projects.map((p) => p.uid),
332
+ [projectDetails.uid]
333
+ );
334
+
335
+ const [result, members, grants] = await Promise.all([
336
+ this.query<AttestationsRes>(refQuery),
337
+ this.membersOf(projects),
338
+ this.grantsFor(projects, true),
339
+ ]);
340
+
341
+ const deps = Attestation.fromInterface(
342
+ result.attestations || [],
343
+ this.network.name
344
+ );
345
+
346
+ return projects.map((project) => {
347
+ project.details = <ProjectDetails>(
348
+ deps.find(
349
+ (ref) =>
350
+ ref.schema.uid === projectDetails.uid && ref.refUID === project.uid
351
+ )
352
+ );
353
+
354
+ project.members = members.filter((m) => m.refUID === project.uid);
355
+
356
+ project.grants = grants.filter((g) => g.refUID === project.uid);
357
+
358
+ return project;
359
+ });
360
+ }
361
+
362
+ async projectById(uid: Hex) {
363
+ const query = gqlQueries.attestation(uid);
364
+ const { attestation } = await this.query<AttestationRes>(query);
365
+
366
+ if (!attestation) throw new Error('Project not found.');
367
+
368
+ const projectAttestation = Attestation.fromInterface<Project>(
369
+ [attestation],
370
+ this.network.name
371
+ )[0];
372
+
373
+ const [result] = await this.projectsDetails([
374
+ new Project(projectAttestation),
375
+ ]);
376
+
377
+ return result;
378
+ }
379
+
380
+ async projectBySlug(slug: string) {
381
+ const projectDetails = this.gap.findSchema('ProjectDetails');
382
+
383
+ const query = gqlQueries.attestationsOf(
384
+ projectDetails.uid,
385
+ this.getSearchFieldString('slug', slug)
386
+ );
387
+
388
+ const {
389
+ schema: { attestations },
390
+ } = await this.query<SchemaRes>(query);
391
+
392
+ const projectAttestations = Attestation.fromInterface<ProjectDetails>(
393
+ attestations,
394
+ this.network.name
395
+ ).filter((p) => p.title);
396
+
397
+ if (!projectAttestations.length) throw new Error('Project not found.');
398
+
399
+ const project = new Project({
400
+ data: { project: true },
401
+ uid: projectAttestations[0].refUID,
402
+ schema: this.gap.findSchema('Project'),
403
+ recipient: projectAttestations[0].recipient,
404
+ });
405
+ const [withDetails] = await this.projectsDetails([project]);
406
+
407
+ if (!withDetails) throw new Error('Project not found.');
408
+
409
+ return withDetails;
410
+ }
411
+
412
+ async slugExists(slug: string) {
413
+ const details = this.gap.findSchema('ProjectDetails');
414
+
415
+ const query = gqlQueries.attestationsOf(details.uid, 'slug');
416
+ const {
417
+ schema: { attestations },
418
+ } = await this.query<SchemaRes>(query);
419
+
420
+ return attestations.some((a) => a.decodedDataJson.includes(slug));
421
+ }
422
+
423
+ searchProjects(query: string): Promise<Project[]> {
424
+ throw new Error('Method not implemented.');
425
+ }
426
+
427
+ async projects(name?: string): Promise<Project[]> {
428
+ const result = await this.attestations('Project', name);
429
+
430
+ if (!result.length) return [];
431
+ const projects = Attestation.fromInterface<Project>(
432
+ result,
433
+ this.network.name
434
+ );
435
+ return this.projectsDetails(projects);
436
+ }
437
+
438
+ async projectsOf(grantee: Hex): Promise<Project[]> {
439
+ const result = await this.attestationsTo('Project', grantee);
440
+
441
+ if (!result.length) return [];
442
+ const projects = Attestation.fromInterface<Project>(
443
+ result,
444
+ this.network.name
445
+ );
446
+ return this.projectsDetails(projects);
447
+ }
448
+
449
+ async grantee(address: Hex): Promise<Grantee> {
450
+ const projects = await this.projectsOf(address);
451
+
452
+ return new Grantee(address, projects);
453
+ }
454
+
455
+ async grantees(): Promise<Grantee[]> {
456
+ const projects = await this.projects();
457
+
458
+ return projects.reduce(
459
+ (acc, item) => {
460
+ const hasGrantee = acc.find((g) => g.address === item.recipient);
461
+
462
+ if (hasGrantee) hasGrantee.projects.push(item);
463
+ else acc.push(new Grantee(item.recipient, [item]));
464
+ return acc;
465
+ },
466
+ <Grantee[]>[]
467
+ );
468
+ }
469
+
470
+ async grantsOf(grantee: Hex, withCommunity?: boolean): Promise<Grant[]> {
471
+ const [grant, grantDetails, grantVerified] = this.gap.findManySchemas([
472
+ 'Grant',
473
+ 'GrantDetails',
474
+ 'GrantVerified',
475
+ ]);
476
+
477
+ const query = gqlQueries.attestationsTo(grant.uid, grantee);
478
+ const {
479
+ schema: { attestations },
480
+ } = await this.query<SchemaRes>(query);
481
+
482
+ const grants = Attestation.fromInterface<Grant>(
483
+ attestations,
484
+ this.network.name
485
+ );
486
+
487
+ if (!grants.length) return [];
488
+
489
+ const ref = gqlQueries.dependentsOf(
490
+ grants.map((g) => g.uid),
491
+ [grantDetails.uid, grantVerified.uid],
492
+ grants.map((g) => g.recipient)
493
+ );
494
+
495
+ const results = await this.query<AttestationsRes>(ref);
496
+ const deps = Attestation.fromInterface(
497
+ results.attestations || [],
498
+ this.network.name
499
+ );
500
+
501
+ const milestones = await this.milestonesOf(grants);
502
+ const communities = withCommunity
503
+ ? await this.communitiesByIds(
504
+ mapFilter(
505
+ grants,
506
+ (g) => !!g.communityUID,
507
+ (g) => g.communityUID
508
+ )
509
+ )
510
+ : [];
511
+
512
+ const withDetails = grants.map((grant) => {
513
+ const refs = deps.filter((ref) => ref.refUID === grant.uid);
514
+
515
+ grant.verified = !!refs.find(
516
+ (ref) =>
517
+ ref.schema.uid === grantVerified.uid && ref.refUID === grant.uid
518
+ );
519
+
520
+ grant.details = <GrantDetails>(
521
+ refs.find(
522
+ (ref) =>
523
+ ref.schema.uid === grantDetails.uid &&
524
+ ref.refUID === grant.uid &&
525
+ typeof (ref as Milestone).endsAt === 'undefined'
526
+ )
527
+ );
528
+
529
+ grant.milestones = milestones.filter(
530
+ (m) => m.refUID === grant.uid && typeof m.endsAt !== 'undefined'
531
+ );
532
+
533
+ grant.community = communities.find((c) => c.uid === grant.communityUID);
534
+
535
+ return grant;
536
+ });
537
+
538
+ return this.grantsUpdates(withDetails);
539
+ }
540
+
541
+ async grantsUpdates(grants: Grant[]) {
542
+ const details = this.gap.findSchema('GrantDetails');
543
+
544
+ const query = gqlQueries.attestationsOf(
545
+ details.uid,
546
+ this.getSearchFieldString('type', 'grant-update'),
547
+ grants.map((g) => g.uid)
548
+ );
549
+
550
+ const {
551
+ schema: { attestations },
552
+ } = await this.query<SchemaRes>(query);
553
+
554
+ const updates = Attestation.fromInterface<GrantUpdate>(
555
+ attestations,
556
+ this.network.name
557
+ );
558
+
559
+ return grants.map((grant) => {
560
+ grant.updates = updates.filter((u) => u.refUID === grant.uid);
561
+ return grant;
562
+ });
563
+ }
564
+
565
+ async grantsByCommunity(uid: Hex) {
566
+ const [grant, grantDetails, project, projectDetails] =
567
+ this.gap.findManySchemas([
568
+ 'Grant',
569
+ 'GrantDetails',
570
+ 'Project',
571
+ 'ProjectDetails',
572
+ ]);
573
+
574
+ const query = gqlQueries.attestations(grant.uid, uid);
575
+ const {
576
+ schema: { attestations },
577
+ } = await this.query<SchemaRes>(query);
578
+
579
+ const grants = Attestation.fromInterface<Grant>(
580
+ attestations,
581
+ this.network.name
582
+ ).map((g) => new Grant(g));
583
+
584
+ if (!grants.length) return [];
585
+
586
+ const refs = gqlQueries.dependentsOf(
587
+ grants.map((g) => [g.uid, g.refUID]).flat(),
588
+ [grantDetails.uid, project.uid]
589
+ );
590
+
591
+ const results = await this.query<AttestationsRes>(refs);
592
+
593
+ const deps = Attestation.fromInterface(
594
+ results.attestations || [],
595
+ this.network.name
596
+ );
597
+
598
+ const projectsQuery = gqlQueries.attestationsIn(
599
+ grants.map((g) => g.refUID)
600
+ );
601
+
602
+ const { attestations: projectAttestations } =
603
+ await this.query<AttestationsRes>(projectsQuery);
604
+
605
+ const projects = Attestation.fromInterface<Project>(
606
+ projectAttestations,
607
+ this.network.name
608
+ );
609
+
610
+ const milestones = await this.milestonesOf(grants);
611
+
612
+ const getSummaryProject = (project: Project) => ({
613
+ title: project.details?.title,
614
+ uid: project.uid,
615
+ slug: project.details?.slug,
616
+ });
617
+
618
+ return grants
619
+ .map((grant) => {
620
+ grant.project = getSummaryProject(
621
+ <Project>projects.find((p) => p.uid === grant.refUID)
622
+ );
623
+ grant.details = <GrantDetails>(
624
+ deps.find(
625
+ (d) =>
626
+ d.refUID === grant.uid &&
627
+ d.schema.uid === grantDetails.uid &&
628
+ typeof (d as GrantDetails).amount !== undefined &&
629
+ typeof (d as Milestone).endsAt === 'undefined' &&
630
+ typeof (d as GrantUpdate).data.type === 'undefined'
631
+ )
632
+ );
633
+
634
+ grant.milestones = milestones
635
+ .filter(
636
+ (m) => m.refUID === grant.uid && typeof m.endsAt !== 'undefined'
637
+ )
638
+ .sort((a, b) => a.endsAt - b.endsAt);
639
+
640
+ grant.updates = deps.filter(
641
+ (d: GrantUpdate) => d.data.type && d.refUID === grant.uid
642
+ ) as GrantUpdate[];
643
+
644
+ return grant;
645
+ })
646
+ .filter((g) => !!g.project);
647
+ }
648
+
649
+ async grantsFor(
650
+ projects: Project[],
651
+ withCommunity?: boolean
652
+ ): Promise<Grant[]> {
653
+ const [grant, grantDetails] = this.gap.findManySchemas([
654
+ 'Grant',
655
+ 'GrantDetails',
656
+ 'Milestone',
657
+ 'MilestoneApproved',
658
+ 'MilestoneCompleted',
659
+ ]);
660
+
661
+ const query = gqlQueries.dependentsOf(
662
+ projects.map((p) => p.uid),
663
+ [grant.uid]
664
+ );
665
+
666
+ const { attestations: grants } = await this.query<AttestationsRes>(query);
667
+
668
+ const grantsWithDetails = Attestation.fromInterface<Grant>(
669
+ grants,
670
+ this.network.name
671
+ ).map((g) => new Grant(g));
672
+
673
+ const ref = gqlQueries.dependentsOf(
674
+ grants.map((g) => g.uid),
675
+ [grantDetails.uid]
676
+ );
677
+
678
+ const { attestations } = await this.query<AttestationsRes>(ref);
679
+
680
+ const milestones = await this.milestonesOf(grantsWithDetails);
681
+
682
+ const deps = Attestation.fromInterface(attestations, this.network.name);
683
+
684
+ // TODO unify this with grantsOf
685
+ grantsWithDetails.forEach((grant) => {
686
+ grant.details = <GrantDetails>(
687
+ deps.find(
688
+ (d) =>
689
+ d.refUID === grant.uid &&
690
+ d.schema.uid === grantDetails.uid &&
691
+ typeof (d as GrantDetails).amount !== undefined &&
692
+ typeof (d as Milestone).endsAt === 'undefined' &&
693
+ typeof (d as GrantUpdate).data.type === 'undefined'
694
+ )
695
+ );
696
+ grant.milestones = milestones
697
+ .filter(
698
+ (m) => m.refUID === grant.uid && typeof m.endsAt !== 'undefined'
699
+ )
700
+ .sort((a, b) => a.endsAt - b.endsAt);
701
+ });
702
+
703
+ const communities = withCommunity
704
+ ? await this.communitiesByIds(
705
+ mapFilter(
706
+ grantsWithDetails,
707
+ (g) => !!g.communityUID,
708
+ (g) => g.communityUID
709
+ )
710
+ )
711
+ : [];
712
+
713
+ grantsWithDetails.forEach((grant) => {
714
+ grant.community = communities.find((c) => c.uid === grant.communityUID);
715
+ });
716
+
717
+ const grantsWithUpdates = await this.grantsUpdates(grantsWithDetails);
718
+
719
+ return grantsWithUpdates.sort(
720
+ (a, b) =>
721
+ a.milestones?.at(-1)?.endsAt - b.milestones?.at(-1)?.endsAt ||
722
+ a.createdAt.getTime() - b.createdAt.getTime()
723
+ );
724
+ }
725
+
726
+ async milestonesOf(grants: Grant[]): Promise<Milestone[]> {
727
+ const [milestone, milestoneApproved, milestoneCompleted] =
728
+ this.gap.findManySchemas([
729
+ 'Milestone',
730
+ 'MilestoneApproved',
731
+ 'MilestoneCompleted',
732
+ ]);
733
+
734
+ const query = gqlQueries.dependentsOf(
735
+ grants.map((g) => g.uid),
736
+ [milestone.uid]
737
+ );
738
+
739
+ const { attestations } = await this.query<AttestationsRes>(query);
740
+
741
+ const milestones = Attestation.fromInterface<Milestone>(
742
+ attestations,
743
+ this.network.name
744
+ )
745
+ .map((milestone) => new Milestone(milestone))
746
+ .filter((m) => typeof m.endsAt !== 'undefined');
747
+
748
+ if (!milestones.length) return [];
749
+
750
+ const ref = gqlQueries.dependentsOf(
751
+ milestones.map((m) => m.uid),
752
+ [milestoneApproved.uid, milestoneCompleted.uid]
753
+ );
754
+
755
+ const results = await this.query<AttestationsRes>(ref);
756
+
757
+ const deps = Attestation.fromInterface<MilestoneCompleted>(
758
+ results.attestations || [],
759
+ this.network.name
760
+ );
761
+
762
+ return milestones.map((milestone) => {
763
+ const refs = deps.filter((ref) => ref.refUID === milestone.uid);
764
+
765
+ milestone.endsAt = toUnix(milestone.endsAt);
766
+
767
+ milestone.completed = refs.find(
768
+ (dep) => dep.type === 'completed' && dep.refUID === milestone.uid
769
+ );
770
+
771
+ milestone.approved = refs.find(
772
+ (dep) => dep.type === 'approved' && dep.refUID === milestone.uid
773
+ );
774
+
775
+ milestone.rejected = refs.find(
776
+ (dep) => dep.type === 'rejected' && dep.refUID === milestone.uid
777
+ );
778
+
779
+ return milestone;
780
+ });
781
+ }
782
+
783
+ async membersOf(projects: Project[]): Promise<MemberOf[]> {
784
+ const [member, memberDetails] = this.gap.findManySchemas([
785
+ 'MemberOf',
786
+ 'MemberDetails',
787
+ ]);
788
+
789
+ if (!projects.length) return [];
790
+
791
+ const query = gqlQueries.dependentsOf(
792
+ projects.map((p) => p.uid),
793
+ [member.uid],
794
+ projects.map((p) => p.attester)
795
+ );
796
+
797
+ const results = await this.query<AttestationsRes>(query);
798
+
799
+ const members = Attestation.fromInterface<MemberOf>(
800
+ results.attestations || [],
801
+ this.network.name
802
+ );
803
+
804
+ if (members.length) {
805
+ const ref = gqlQueries.dependentsOf(
806
+ members.map((a) => a.uid),
807
+ [memberDetails.uid],
808
+ members.map((a) => a.attester)
809
+ );
810
+
811
+ const detailsResult = await this.query<AttestationsRes>(ref);
812
+ const detailsRef = Attestation.fromInterface<MemberDetails>(
813
+ detailsResult.attestations || [],
814
+ this.network.name
815
+ );
816
+
817
+ members.forEach((member) => {
818
+ member.details = detailsRef.find((d) => d.refUID === member.uid);
819
+ });
820
+ }
821
+
822
+ return members;
823
+ }
824
+
825
+ /**
826
+ * Returns a string to be used to search by a value in `decodedDataJson`.
827
+ * @param field
828
+ * @param value
829
+ */
830
+ private getSearchFieldString(field: string, value: string) {
831
+ return [
832
+ String.raw`\\\\\"${field}\\\\\":\\\\\"${value}\\\\\"`,
833
+ String.raw`\\\\\"${field}\\\\\": \\\\\"${value}\\\\\"`,
834
+ ];
835
+ }
836
+
837
+ async grantsForExtProject(projectExtId: string): Promise<Grant[]> {
838
+ console.error(
839
+ new Error(
840
+ 'Grants for external project is only supported by a custom indexer. Check https://github.com/show-karma/karma-gap-sdk for more information.'
841
+ )
842
+ );
843
+ return [];
844
+ }
845
+ }