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

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 (71) hide show
  1. package/core/abi/CommunityResolverABI.json +508 -0
  2. package/core/class/AttestationIPFS.d.ts +7 -0
  3. package/core/class/AttestationIPFS.js +10 -0
  4. package/core/class/GAP.d.ts +7 -0
  5. package/core/class/GAP.js +14 -0
  6. package/core/class/GraphQL/Fetcher.d.ts +132 -0
  7. package/core/class/GraphQL/Fetcher.js +7 -0
  8. package/core/class/GraphQL/GAPFetcher.d.ts +160 -0
  9. package/core/class/GraphQL/GAPFetcher.js +516 -0
  10. package/core/class/IPFS/IPFS.d.ts +13 -0
  11. package/core/class/IPFS/IPFS.js +24 -0
  12. package/core/class/contract/MultiAttest.d.ts +10 -0
  13. package/core/class/contract/MultiAttest.js +19 -0
  14. package/core/consts.js +4 -34
  15. package/core/types.d.ts +1 -0
  16. package/package.json +1 -1
  17. package/readme.md +34 -39
  18. package/config/keys.example.json +0 -6
  19. package/core/abi/EAS.json +0 -1
  20. package/core/abi/SchemaRegistry.json +0 -1
  21. package/core/class/Attestation.ts +0 -402
  22. package/core/class/Fetcher.ts +0 -202
  23. package/core/class/GAP.ts +0 -398
  24. package/core/class/GapSchema.ts +0 -90
  25. package/core/class/Gelato/Gelato.ts +0 -286
  26. package/core/class/GraphQL/AxiosGQL.ts +0 -29
  27. package/core/class/GraphQL/EASClient.ts +0 -34
  28. package/core/class/GraphQL/GapEasClient.ts +0 -845
  29. package/core/class/GraphQL/index.ts +0 -3
  30. package/core/class/Schema.ts +0 -609
  31. package/core/class/SchemaError.ts +0 -36
  32. package/core/class/contract/GapContract.ts +0 -353
  33. package/core/class/entities/Community.ts +0 -115
  34. package/core/class/entities/Grant.ts +0 -309
  35. package/core/class/entities/MemberOf.ts +0 -42
  36. package/core/class/entities/Milestone.ts +0 -269
  37. package/core/class/entities/Project.ts +0 -370
  38. package/core/class/entities/index.ts +0 -5
  39. package/core/class/index.ts +0 -10
  40. package/core/class/karma-indexer/GapIndexerClient.ts +0 -245
  41. package/core/class/remote-storage/IpfsStorage.ts +0 -51
  42. package/core/class/remote-storage/RemoteStorage.ts +0 -65
  43. package/core/class/types/attestations.ts +0 -158
  44. package/core/consts.ts +0 -282
  45. package/core/index.ts +0 -7
  46. package/core/scripts/deploy.ts +0 -67
  47. package/core/scripts/index.ts +0 -1
  48. package/core/types.ts +0 -186
  49. package/core/utils/gelato/index.ts +0 -3
  50. package/core/utils/gelato/send-gelato-txn.ts +0 -114
  51. package/core/utils/gelato/sponsor-handler.ts +0 -77
  52. package/core/utils/gelato/watch-gelato-txn.ts +0 -67
  53. package/core/utils/get-date.ts +0 -3
  54. package/core/utils/get-ipfs-data.ts +0 -13
  55. package/core/utils/get-web3-provider.ts +0 -20
  56. package/core/utils/gql-queries.ts +0 -133
  57. package/core/utils/index.ts +0 -7
  58. package/core/utils/map-filter.ts +0 -21
  59. package/core/utils/serialize-bigint.ts +0 -7
  60. package/core/utils/to-unix.ts +0 -18
  61. package/csv-upload/.gitkeep +0 -0
  62. package/csv-upload/example.csv +0 -2
  63. package/csv-upload/scripts/run.ts +0 -193
  64. package/docs/.gitkeep +0 -0
  65. package/docs/images/attestation-architecture.png +0 -0
  66. package/docs/images/dfd-get-projects.png +0 -0
  67. package/index.ts +0 -1
  68. package/schemas/.gitkeep +0 -0
  69. package/schemas/GAP-schemas-1692135812877.json +0 -33
  70. package/test-file.ts +0 -92
  71. package/tsconfig.json +0 -26
@@ -0,0 +1,508 @@
1
+ [
2
+ {
3
+ "inputs": [
4
+ {
5
+ "internalType": "contract IEAS",
6
+ "name": "eas",
7
+ "type": "address"
8
+ }
9
+ ],
10
+ "stateMutability": "nonpayable",
11
+ "type": "constructor"
12
+ },
13
+ {
14
+ "inputs": [],
15
+ "name": "AccessDenied",
16
+ "type": "error"
17
+ },
18
+ {
19
+ "inputs": [],
20
+ "name": "InsufficientValue",
21
+ "type": "error"
22
+ },
23
+ {
24
+ "inputs": [],
25
+ "name": "InvalidEAS",
26
+ "type": "error"
27
+ },
28
+ {
29
+ "inputs": [],
30
+ "name": "NotPayable",
31
+ "type": "error"
32
+ },
33
+ {
34
+ "anonymous": false,
35
+ "inputs": [
36
+ {
37
+ "indexed": false,
38
+ "internalType": "uint8",
39
+ "name": "version",
40
+ "type": "uint8"
41
+ }
42
+ ],
43
+ "name": "Initialized",
44
+ "type": "event"
45
+ },
46
+ {
47
+ "anonymous": false,
48
+ "inputs": [
49
+ {
50
+ "indexed": true,
51
+ "internalType": "address",
52
+ "name": "previousOwner",
53
+ "type": "address"
54
+ },
55
+ {
56
+ "indexed": true,
57
+ "internalType": "address",
58
+ "name": "newOwner",
59
+ "type": "address"
60
+ }
61
+ ],
62
+ "name": "OwnershipTransferred",
63
+ "type": "event"
64
+ },
65
+ {
66
+ "inputs": [
67
+ {
68
+ "components": [
69
+ {
70
+ "internalType": "bytes32",
71
+ "name": "uid",
72
+ "type": "bytes32"
73
+ },
74
+ {
75
+ "internalType": "bytes32",
76
+ "name": "schema",
77
+ "type": "bytes32"
78
+ },
79
+ {
80
+ "internalType": "uint64",
81
+ "name": "time",
82
+ "type": "uint64"
83
+ },
84
+ {
85
+ "internalType": "uint64",
86
+ "name": "expirationTime",
87
+ "type": "uint64"
88
+ },
89
+ {
90
+ "internalType": "uint64",
91
+ "name": "revocationTime",
92
+ "type": "uint64"
93
+ },
94
+ {
95
+ "internalType": "bytes32",
96
+ "name": "refUID",
97
+ "type": "bytes32"
98
+ },
99
+ {
100
+ "internalType": "address",
101
+ "name": "recipient",
102
+ "type": "address"
103
+ },
104
+ {
105
+ "internalType": "address",
106
+ "name": "attester",
107
+ "type": "address"
108
+ },
109
+ {
110
+ "internalType": "bool",
111
+ "name": "revocable",
112
+ "type": "bool"
113
+ },
114
+ {
115
+ "internalType": "bytes",
116
+ "name": "data",
117
+ "type": "bytes"
118
+ }
119
+ ],
120
+ "internalType": "struct Attestation",
121
+ "name": "attestation",
122
+ "type": "tuple"
123
+ }
124
+ ],
125
+ "name": "attest",
126
+ "outputs": [
127
+ {
128
+ "internalType": "bool",
129
+ "name": "",
130
+ "type": "bool"
131
+ }
132
+ ],
133
+ "stateMutability": "payable",
134
+ "type": "function"
135
+ },
136
+ {
137
+ "inputs": [
138
+ {
139
+ "internalType": "address",
140
+ "name": "attester",
141
+ "type": "address"
142
+ }
143
+ ],
144
+ "name": "canAttest",
145
+ "outputs": [
146
+ {
147
+ "internalType": "bool",
148
+ "name": "",
149
+ "type": "bool"
150
+ }
151
+ ],
152
+ "stateMutability": "view",
153
+ "type": "function"
154
+ },
155
+ {
156
+ "inputs": [
157
+ {
158
+ "internalType": "bytes32",
159
+ "name": "community",
160
+ "type": "bytes32"
161
+ },
162
+ {
163
+ "internalType": "address",
164
+ "name": "addr",
165
+ "type": "address"
166
+ }
167
+ ],
168
+ "name": "delist",
169
+ "outputs": [],
170
+ "stateMutability": "nonpayable",
171
+ "type": "function"
172
+ },
173
+ {
174
+ "inputs": [
175
+ {
176
+ "internalType": "bytes32",
177
+ "name": "community",
178
+ "type": "bytes32"
179
+ },
180
+ {
181
+ "internalType": "address",
182
+ "name": "addr",
183
+ "type": "address"
184
+ }
185
+ ],
186
+ "name": "enlist",
187
+ "outputs": [],
188
+ "stateMutability": "nonpayable",
189
+ "type": "function"
190
+ },
191
+ {
192
+ "inputs": [],
193
+ "name": "initialize",
194
+ "outputs": [],
195
+ "stateMutability": "nonpayable",
196
+ "type": "function"
197
+ },
198
+ {
199
+ "inputs": [
200
+ {
201
+ "internalType": "bytes32",
202
+ "name": "community",
203
+ "type": "bytes32"
204
+ },
205
+ {
206
+ "internalType": "address",
207
+ "name": "addr",
208
+ "type": "address"
209
+ }
210
+ ],
211
+ "name": "isAdmin",
212
+ "outputs": [
213
+ {
214
+ "internalType": "bool",
215
+ "name": "",
216
+ "type": "bool"
217
+ }
218
+ ],
219
+ "stateMutability": "view",
220
+ "type": "function"
221
+ },
222
+ {
223
+ "inputs": [],
224
+ "name": "isPayable",
225
+ "outputs": [
226
+ {
227
+ "internalType": "bool",
228
+ "name": "",
229
+ "type": "bool"
230
+ }
231
+ ],
232
+ "stateMutability": "pure",
233
+ "type": "function"
234
+ },
235
+ {
236
+ "inputs": [
237
+ {
238
+ "components": [
239
+ {
240
+ "internalType": "bytes32",
241
+ "name": "uid",
242
+ "type": "bytes32"
243
+ },
244
+ {
245
+ "internalType": "bytes32",
246
+ "name": "schema",
247
+ "type": "bytes32"
248
+ },
249
+ {
250
+ "internalType": "uint64",
251
+ "name": "time",
252
+ "type": "uint64"
253
+ },
254
+ {
255
+ "internalType": "uint64",
256
+ "name": "expirationTime",
257
+ "type": "uint64"
258
+ },
259
+ {
260
+ "internalType": "uint64",
261
+ "name": "revocationTime",
262
+ "type": "uint64"
263
+ },
264
+ {
265
+ "internalType": "bytes32",
266
+ "name": "refUID",
267
+ "type": "bytes32"
268
+ },
269
+ {
270
+ "internalType": "address",
271
+ "name": "recipient",
272
+ "type": "address"
273
+ },
274
+ {
275
+ "internalType": "address",
276
+ "name": "attester",
277
+ "type": "address"
278
+ },
279
+ {
280
+ "internalType": "bool",
281
+ "name": "revocable",
282
+ "type": "bool"
283
+ },
284
+ {
285
+ "internalType": "bytes",
286
+ "name": "data",
287
+ "type": "bytes"
288
+ }
289
+ ],
290
+ "internalType": "struct Attestation[]",
291
+ "name": "attestations",
292
+ "type": "tuple[]"
293
+ },
294
+ {
295
+ "internalType": "uint256[]",
296
+ "name": "values",
297
+ "type": "uint256[]"
298
+ }
299
+ ],
300
+ "name": "multiAttest",
301
+ "outputs": [
302
+ {
303
+ "internalType": "bool",
304
+ "name": "",
305
+ "type": "bool"
306
+ }
307
+ ],
308
+ "stateMutability": "payable",
309
+ "type": "function"
310
+ },
311
+ {
312
+ "inputs": [
313
+ {
314
+ "components": [
315
+ {
316
+ "internalType": "bytes32",
317
+ "name": "uid",
318
+ "type": "bytes32"
319
+ },
320
+ {
321
+ "internalType": "bytes32",
322
+ "name": "schema",
323
+ "type": "bytes32"
324
+ },
325
+ {
326
+ "internalType": "uint64",
327
+ "name": "time",
328
+ "type": "uint64"
329
+ },
330
+ {
331
+ "internalType": "uint64",
332
+ "name": "expirationTime",
333
+ "type": "uint64"
334
+ },
335
+ {
336
+ "internalType": "uint64",
337
+ "name": "revocationTime",
338
+ "type": "uint64"
339
+ },
340
+ {
341
+ "internalType": "bytes32",
342
+ "name": "refUID",
343
+ "type": "bytes32"
344
+ },
345
+ {
346
+ "internalType": "address",
347
+ "name": "recipient",
348
+ "type": "address"
349
+ },
350
+ {
351
+ "internalType": "address",
352
+ "name": "attester",
353
+ "type": "address"
354
+ },
355
+ {
356
+ "internalType": "bool",
357
+ "name": "revocable",
358
+ "type": "bool"
359
+ },
360
+ {
361
+ "internalType": "bytes",
362
+ "name": "data",
363
+ "type": "bytes"
364
+ }
365
+ ],
366
+ "internalType": "struct Attestation[]",
367
+ "name": "attestations",
368
+ "type": "tuple[]"
369
+ },
370
+ {
371
+ "internalType": "uint256[]",
372
+ "name": "values",
373
+ "type": "uint256[]"
374
+ }
375
+ ],
376
+ "name": "multiRevoke",
377
+ "outputs": [
378
+ {
379
+ "internalType": "bool",
380
+ "name": "",
381
+ "type": "bool"
382
+ }
383
+ ],
384
+ "stateMutability": "payable",
385
+ "type": "function"
386
+ },
387
+ {
388
+ "inputs": [],
389
+ "name": "owner",
390
+ "outputs": [
391
+ {
392
+ "internalType": "address",
393
+ "name": "",
394
+ "type": "address"
395
+ }
396
+ ],
397
+ "stateMutability": "view",
398
+ "type": "function"
399
+ },
400
+ {
401
+ "inputs": [],
402
+ "name": "renounceOwnership",
403
+ "outputs": [],
404
+ "stateMutability": "nonpayable",
405
+ "type": "function"
406
+ },
407
+ {
408
+ "inputs": [
409
+ {
410
+ "components": [
411
+ {
412
+ "internalType": "bytes32",
413
+ "name": "uid",
414
+ "type": "bytes32"
415
+ },
416
+ {
417
+ "internalType": "bytes32",
418
+ "name": "schema",
419
+ "type": "bytes32"
420
+ },
421
+ {
422
+ "internalType": "uint64",
423
+ "name": "time",
424
+ "type": "uint64"
425
+ },
426
+ {
427
+ "internalType": "uint64",
428
+ "name": "expirationTime",
429
+ "type": "uint64"
430
+ },
431
+ {
432
+ "internalType": "uint64",
433
+ "name": "revocationTime",
434
+ "type": "uint64"
435
+ },
436
+ {
437
+ "internalType": "bytes32",
438
+ "name": "refUID",
439
+ "type": "bytes32"
440
+ },
441
+ {
442
+ "internalType": "address",
443
+ "name": "recipient",
444
+ "type": "address"
445
+ },
446
+ {
447
+ "internalType": "address",
448
+ "name": "attester",
449
+ "type": "address"
450
+ },
451
+ {
452
+ "internalType": "bool",
453
+ "name": "revocable",
454
+ "type": "bool"
455
+ },
456
+ {
457
+ "internalType": "bytes",
458
+ "name": "data",
459
+ "type": "bytes"
460
+ }
461
+ ],
462
+ "internalType": "struct Attestation",
463
+ "name": "attestation",
464
+ "type": "tuple"
465
+ }
466
+ ],
467
+ "name": "revoke",
468
+ "outputs": [
469
+ {
470
+ "internalType": "bool",
471
+ "name": "",
472
+ "type": "bool"
473
+ }
474
+ ],
475
+ "stateMutability": "payable",
476
+ "type": "function"
477
+ },
478
+ {
479
+ "inputs": [
480
+ {
481
+ "internalType": "address",
482
+ "name": "newOwner",
483
+ "type": "address"
484
+ }
485
+ ],
486
+ "name": "transferOwnership",
487
+ "outputs": [],
488
+ "stateMutability": "nonpayable",
489
+ "type": "function"
490
+ },
491
+ {
492
+ "inputs": [],
493
+ "name": "version",
494
+ "outputs": [
495
+ {
496
+ "internalType": "string",
497
+ "name": "",
498
+ "type": "string"
499
+ }
500
+ ],
501
+ "stateMutability": "view",
502
+ "type": "function"
503
+ },
504
+ {
505
+ "stateMutability": "payable",
506
+ "type": "receive"
507
+ }
508
+ ]
@@ -0,0 +1,7 @@
1
+ import { IPFS } from './IPFS/IPFS';
2
+ export declare class AttestationIPFS extends IPFS {
3
+ encode(data: string, storageType: number): {
4
+ ipfsHash: string;
5
+ type: number;
6
+ };
7
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AttestationIPFS = void 0;
4
+ const IPFS_1 = require("./IPFS/IPFS");
5
+ class AttestationIPFS extends IPFS_1.IPFS {
6
+ encode(data, storageType) {
7
+ return { ipfsHash: data, type: storageType };
8
+ }
9
+ }
10
+ exports.AttestationIPFS = AttestationIPFS;
@@ -208,6 +208,13 @@ export declare class GAP extends Facade {
208
208
  static getProjectResolver(signer: SignerOrProvider & {
209
209
  getChainId?: () => Promise<number>;
210
210
  }, chainId?: number): Promise<ethers.Contract>;
211
+ /**
212
+ * Get the multicall contract
213
+ * @param signer
214
+ */
215
+ static getCommunityResolver(signer: SignerOrProvider & {
216
+ getChainId?: () => Promise<number>;
217
+ }, chainId?: number): Promise<ethers.Contract>;
211
218
  get schemas(): GapSchema[];
212
219
  /**
213
220
  * Defined if the transactions will be gasless or not.
package/core/class/GAP.js CHANGED
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.GAP = void 0;
7
7
  const MultiAttester_json_1 = __importDefault(require("../abi/MultiAttester.json"));
8
8
  const ProjectResolver_json_1 = __importDefault(require("../abi/ProjectResolver.json"));
9
+ const CommunityResolverABI_json_1 = __importDefault(require("../abi/CommunityResolverABI.json"));
9
10
  const types_1 = require("../types");
10
11
  const Schema_1 = require("./Schema");
11
12
  const GapSchema_1 = require("./GapSchema");
@@ -195,6 +196,19 @@ class GAP extends types_1.Facade {
195
196
  const address = network.contracts.projectResolver;
196
197
  return new ethers_1.ethers.Contract(address, ProjectResolver_json_1.default, provider);
197
198
  }
199
+ /**
200
+ * Get the multicall contract
201
+ * @param signer
202
+ */
203
+ static async getCommunityResolver(signer, chainId) {
204
+ const currentChainId = chainId ||
205
+ Number((await signer.provider.getNetwork())?.chainId ||
206
+ (await signer.getChainId()));
207
+ const provider = chainId ? (0, get_web3_provider_1.getWeb3Provider)(chainId) : signer;
208
+ const network = Object.values(consts_1.Networks).find((n) => +n.chainId === Number(currentChainId));
209
+ const address = network.contracts.communityResolver;
210
+ return new ethers_1.ethers.Contract(address, CommunityResolverABI_json_1.default, provider);
211
+ }
198
212
  get schemas() {
199
213
  return this._schemas;
200
214
  }
@@ -0,0 +1,132 @@
1
+ import { Hex, IAttestation, TSchemaName } from "core/types";
2
+ import { Attestation } from "../Attestation";
3
+ import { Community, Grant, MemberOf, Milestone, Project } from "../entities";
4
+ import { Grantee } from "../types/attestations";
5
+ import { AxiosGQL } from "./AxiosGQL";
6
+ export declare abstract class Fetcher extends AxiosGQL {
7
+ /**
8
+ * Fetch a single attestation by its UID.
9
+ * @param uid
10
+ */
11
+ abstract attestation<T = unknown>(uid: Hex): Promise<Attestation<T>>;
12
+ /**
13
+ * Fetch attestations of a schema.
14
+ * @param schemaName
15
+ * @param search if set, will search decodedDataJson by the value.
16
+ * @returns
17
+ */
18
+ abstract attestations(schemaName: TSchemaName, search?: string): Promise<IAttestation[]>;
19
+ /**
20
+ * Fetch attestations of a schema.
21
+ * @param schemaName
22
+ * @param recipient
23
+ * @returns
24
+ */
25
+ abstract attestationsOf(schemaName: TSchemaName, recipient: Hex): Promise<IAttestation[]>;
26
+ /**
27
+ * Fetch attestations of a schema for a specific recipient.
28
+ * @param schemaName
29
+ * @param recipient
30
+ * @returns
31
+ */
32
+ abstract attestationsTo(schemaName: TSchemaName, recipient: Hex): Promise<IAttestation[]>;
33
+ /**
34
+ * Fetch all available communities with details and grantees uids.
35
+ *
36
+ * If search is defined, will try to find communities by the search string.
37
+ * @param search
38
+ * @returns
39
+ */
40
+ abstract communities(search?: string): Promise<Community[]>;
41
+ /**
42
+ * Fetch a set of communities by their ids.
43
+ * @param uids
44
+ * @returns
45
+ */
46
+ abstract communitiesByIds(uids: Hex[]): Promise<Community[]>;
47
+ /**
48
+ * Fetch a community by its name with details, grants and milestones.
49
+ *
50
+ * It is possible that the resulted community is not the one you are looking for.
51
+ * @param name
52
+ * @returns
53
+ */
54
+ abstract communityBySlug(slug: string): Promise<Community>;
55
+ /**
56
+ * Fetch a community by its id. This method will also return the
57
+ * community details and projects.
58
+ */
59
+ abstract communityById(uid: Hex): Promise<Community>;
60
+ /**
61
+ * Fetch a project by its id.
62
+ * @param uid
63
+ * @returns
64
+ */
65
+ abstract projectById(uid: Hex): Promise<Project>;
66
+ /**
67
+ * Fetch a project by its slug.
68
+ * @param slug
69
+ * @returns
70
+ */
71
+ abstract projectBySlug(slug: string): Promise<Project>;
72
+ /**
73
+ * Fetch projects with details and members.
74
+ * @param name if set, will search by the name.
75
+ * @returns
76
+ */
77
+ abstract projects(name?: string): Promise<Project[]>;
78
+ /**
79
+ * Fetch projects with details and members.
80
+ * @param grantee the public address of the grantee
81
+ * @returns
82
+ */
83
+ abstract projectsOf(grantee: Hex): Promise<Project[]>;
84
+ /**
85
+ * Fetch Grantee with details and projects.
86
+ * @param address
87
+ * @param withProjects if true, will get grantee project details.
88
+ * @returns
89
+ */
90
+ abstract grantee(address: Hex): Promise<Grantee>;
91
+ /**
92
+ * Fetch all Grantees with details.
93
+ * @returns
94
+ */
95
+ abstract grantees(): Promise<Grantee[]>;
96
+ /**
97
+ * Fetches the grantes related to a grantee address (recipient).
98
+ * @param grantee grantee address
99
+ * @returns
100
+ */
101
+ abstract grantsOf(grantee: Hex, withCommunity?: boolean): Promise<Grant[]>;
102
+ /**
103
+ * Fetch grants for an array of projects with milestones.
104
+ * @param projects
105
+ * @returns
106
+ */
107
+ abstract grantsFor(projects: Project[], withCommunity?: boolean): Promise<Grant[]>;
108
+ /**
109
+ * Fetch a grants that belongs to a community.
110
+ * @param uid community uid
111
+ * @returns
112
+ */
113
+ abstract grantsByCommunity(uid: Hex): any;
114
+ /**
115
+ * Fetch all milestones related to an array of Grants.
116
+ * @param grants
117
+ * @returns
118
+ */
119
+ abstract milestonesOf(grants: Grant[]): Promise<Milestone[]>;
120
+ /**
121
+ * Bulk fetch members with details of an array of Projects.
122
+ * @param projects
123
+ * @returns
124
+ */
125
+ abstract membersOf(projects: Project[]): Promise<MemberOf[]>;
126
+ /**
127
+ * Check if a name is already in use.
128
+ * @param slug
129
+ * @returns
130
+ */
131
+ abstract slugExists(slug: string): Promise<boolean>;
132
+ }