@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,398 @@
1
+ import MulticallABI from '../abi/MultiAttester.json';
2
+ import ProjectResolverABI from '../abi/ProjectResolver.json';
3
+
4
+ import {
5
+ AttestArgs,
6
+ Facade,
7
+ SchemaInterface,
8
+ TNetwork,
9
+ TSchemaName,
10
+ SignerOrProvider,
11
+ } from '../types';
12
+ import { Schema } from './Schema';
13
+ import { GapSchema } from './GapSchema';
14
+ import { EAS } from '@ethereum-attestation-service/eas-sdk';
15
+ import { MountEntities, Networks } from '../consts';
16
+ import { ethers } from 'ethers';
17
+ import { version } from '../../package.json';
18
+ import { Fetcher } from './Fetcher';
19
+ import { RemoteStorage } from './remote-storage/RemoteStorage';
20
+ import { GapEasClient } from './GraphQL';
21
+ import { getWeb3Provider } from '../utils/get-web3-provider';
22
+
23
+ interface GAPArgs {
24
+ network: TNetwork;
25
+ globalSchemas?: boolean;
26
+ /**
27
+ * Custom API Client to be used to fetch attestation data.
28
+ * If not defined, will use the default EAS Client and rely on EAS's GraphQL API.
29
+ */
30
+ apiClient?: Fetcher;
31
+ schemas?: SchemaInterface<TSchemaName>[];
32
+ /**
33
+ * Defined if the transactions will be gasless or not.
34
+ *
35
+ * In case of true, the transactions will be sent through [Gelato](https://gelato.network)
36
+ * and an API key is needed.
37
+ *
38
+ * > __Note that to safely transact through Gelato, the user must
39
+ * have set a handlerUrl and not expose gelato api in the frontend.__
40
+ */
41
+ gelatoOpts?: {
42
+ /**
43
+ * Endpoint in which the transaction will be sent.
44
+ * A custom endpoint will ensure that the transaction will be sent through Gelato
45
+ * and api keys won't be exposed in the frontend.
46
+ *
47
+ * __If coding a backend, you can use `apiKey` prop instead.__
48
+ *
49
+ * `core/utils/gelato/sponsor-handler.ts` is a base handler that can be used
50
+ * together with NextJS API routes.
51
+ *
52
+ * @example
53
+ *
54
+ * ```ts
55
+ * // pages/api/gelato.ts
56
+ * import { handler as sponsorHandler } from "core/utils/gelato/sponsor-handler";
57
+ *
58
+ * export default const handler(req, res) => sponsorHandler(req, res, "GELATO_API_KEY_ENV_VARIABLE");
59
+ *
60
+ * ```
61
+ */
62
+ sponsorUrl?: string;
63
+ /**
64
+ * If true, env_gelatoApiKey will be marked as required.
65
+ * This means that the endpoint at sponsorUrl is contained in this application.
66
+ *
67
+ * E.g. Next.JS api route.
68
+ */
69
+ contained?: boolean;
70
+ /**
71
+ * The env key of gelato api key that will be used in the handler.
72
+ *
73
+ * @example
74
+ *
75
+ * ```
76
+ * // .env
77
+ * GELATO_API_KEY=1234567890
78
+ *
79
+ * // sponsor-handler.ts
80
+ *
81
+ * export async function handler(req, res) {
82
+ * // ...code
83
+ *
84
+ * const { env_gelatoApiKey } = GAP.gelatoOpts;
85
+ *
86
+ * // Will be used to get the key from environment.
87
+ * const { [env_gelatoApiKey]: apiKey } = process.env;
88
+ *
89
+ * // send txn
90
+ * // res.send(result);
91
+ * }
92
+ * ```
93
+ */
94
+ env_gelatoApiKey?: string;
95
+ /**
96
+ * API key to be used in the handler.
97
+ *
98
+ * @deprecated Use this only if you have no option of setting a backend, next/nuxt api route
99
+ * or if this application is a backend.
100
+ *
101
+ * > __This will expose the api key if used in the frontend.__
102
+ */
103
+ apiKey?: string;
104
+ /**
105
+ * If true, will use gelato to send transactions.
106
+ */
107
+ useGasless?: boolean;
108
+ };
109
+ /**
110
+ * Defines a remote storage client to be used to store data.
111
+ * If defined, all the details data from an attestation will
112
+ * be stored in the remote storage, e.g. IPFS.
113
+ */
114
+ remoteStorage?: RemoteStorage;
115
+ }
116
+
117
+ /**
118
+ * GAP SDK Facade.
119
+ *
120
+ * This is the main class that is used to interact with the GAP SDK.
121
+ *
122
+ * This class can behave as a singleton or as a regular class.
123
+ *
124
+ * Using this class, the user will be able to:
125
+ *
126
+ * - Create and manage attestations
127
+ * - Create and manage schemas
128
+ * - Fetch data from the EAS
129
+ *
130
+ * #### Features
131
+ * - EAS Client: used to interact with EAS contracts
132
+ * - EAS Fetcher: used to fetch data from the EAS GraphQL API, providing methods for:
133
+ * - Get projects
134
+ * - Get grants with its details
135
+ * - Get grantees
136
+ * - Get members
137
+ * - Get tags
138
+ * - Get external links
139
+ * - Get schemas
140
+ * - Get attestations by pair, attester, recipient, schema, or UID
141
+ * - Get dependent attestations
142
+ * - Schema: used to create and manage schemas
143
+ * - Attestation: used to create and manage attestations
144
+ * - Replace schemas: used to replace the schema list with a new list
145
+ * - Replace single schema: used to replace a single schema from the schema list
146
+ *
147
+ * ---
148
+ * @example
149
+ * ```ts
150
+ * import { GAP } from "./core/class/GAP";
151
+ * import { GapSchema } from "./core/class/GapSchema";
152
+ * import { Schema } from "./core/class/Schema";
153
+ * import { MountEntities, Networks } from "./core/consts";
154
+ *
155
+ * const schemas = MountEntities(Networks.sepolia);
156
+ *
157
+ * const gap = new GAP({
158
+ * network: "sepolia",
159
+ * owner: "0xd7d1DB401EA825b0325141Cd5e6cd7C2d01825f2",
160
+ * schemas: Object.values(schemas),
161
+ * });
162
+ *
163
+ * gap.fetcher
164
+ * .fetchProjects()
165
+ * .then((res) => {
166
+ * console.log(JSON.stringify(res, null, 2));
167
+ * })
168
+ *
169
+ * ```
170
+ */
171
+ export class GAP extends Facade {
172
+ private static remoteStorage?: RemoteStorage;
173
+
174
+ readonly fetch: Fetcher;
175
+ readonly network: TNetwork;
176
+
177
+ private _schemas: GapSchema[];
178
+ private static _gelatoOpts = null;
179
+
180
+ constructor(args: GAPArgs) {
181
+ super();
182
+
183
+ const schemas =
184
+ args.schemas || Object.values(MountEntities(Networks[args.network]));
185
+
186
+ this.network = args.network;
187
+
188
+ this._eas = new EAS(Networks[args.network].contracts.eas);
189
+
190
+ this.fetch =
191
+ args.apiClient ||
192
+ new GapEasClient({
193
+ network: args.network,
194
+ });
195
+
196
+ this.fetch.gapInstance = this;
197
+
198
+ this.assertGelatoOpts(args);
199
+ GAP._gelatoOpts = args.gelatoOpts;
200
+
201
+ GAP.remoteStorage = args.remoteStorage;
202
+
203
+ this._schemas = schemas.map(
204
+ (schema) =>
205
+ new GapSchema(
206
+ schema,
207
+ this,
208
+ false,
209
+ args.globalSchemas ? !args.globalSchemas : false
210
+ )
211
+ );
212
+
213
+ Schema.validate(this.network);
214
+
215
+ console.info(`Loaded GAP SDK v${version} for network ${this.network}`);
216
+ }
217
+
218
+ private assertGelatoOpts(args: GAPArgs) {
219
+ if (
220
+ args.gelatoOpts &&
221
+ !(args.gelatoOpts.sponsorUrl || args.gelatoOpts.apiKey)
222
+ ) {
223
+ throw new Error('You must provide a `sponsorUrl` or an `apiKey`.');
224
+ }
225
+
226
+ if (
227
+ args.gelatoOpts?.sponsorUrl &&
228
+ args.gelatoOpts?.contained &&
229
+ !args.gelatoOpts.env_gelatoApiKey
230
+ ) {
231
+ throw new Error(
232
+ 'You must provide `env_gelatoApiKey` to be able to use it in a backend handler.'
233
+ );
234
+ }
235
+
236
+ if (
237
+ (args.gelatoOpts?.env_gelatoApiKey ||
238
+ args.gelatoOpts?.apiKey ||
239
+ args.gelatoOpts?.sponsorUrl) &&
240
+ !args.gelatoOpts?.useGasless
241
+ ) {
242
+ console.warn(
243
+ 'GAP::You are using gelatoOpts but not setting useGasless to true. This will send transactions through the normal provider.'
244
+ );
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Creates the attestation payload using a specific schema.
250
+ * @param from
251
+ * @param to
252
+ * @param data
253
+ * @param schema
254
+ */
255
+ async attest<T>(attestation: AttestArgs<T> & { schemaName: TSchemaName }) {
256
+ const schema = GapSchema.find(attestation.schemaName, this.network);
257
+ return schema.attest(attestation);
258
+ }
259
+
260
+ /**
261
+ * Replaces the schema list with a new list.
262
+ * @param schemas
263
+ */
264
+ replaceSchemas(schemas: GapSchema[]) {
265
+ Schema.replaceAll(schemas, this.network);
266
+ }
267
+
268
+ /**
269
+ * Replaces a schema from the schema list.
270
+ * @throws {SchemaError} if desired schema name does not exist.
271
+ */
272
+ replaceSingleSchema(schema: GapSchema) {
273
+ Schema.replaceOne(schema, this.network);
274
+ }
275
+
276
+ /**
277
+ * Generates a slug from a text.
278
+ * @param text
279
+ * @returns
280
+ */
281
+ generateSlug = async (text: string): Promise<string> => {
282
+ let slug = text
283
+ .toLowerCase()
284
+ .replace(/ /g, '-')
285
+ .replace(/[^\w-]+/g, '');
286
+ const slugExists = await this.fetch.slugExists(slug);
287
+
288
+ if (slugExists) {
289
+ const parts = slug.split('-');
290
+ const counter = parts.pop();
291
+ slug = /\d+/g.test(counter) ? parts.join('-') : slug;
292
+ // eslint-disable-next-line no-param-reassign
293
+ const nextSlug = `${slug}-${
294
+ counter && /\d+/g.test(counter) ? +counter + 1 : 1
295
+ }`;
296
+ console.log({ nextSlug, counter, slug });
297
+ return this.generateSlug(nextSlug);
298
+ }
299
+
300
+ return slug.toLowerCase();
301
+ };
302
+
303
+ /**
304
+ * Returns a copy of the original schema with no pointers.
305
+ * @param name
306
+ * @returns
307
+ */
308
+ findSchema(name: TSchemaName): GapSchema {
309
+ const found = Schema.get<TSchemaName, GapSchema>(name, this.network);
310
+ return GapSchema.clone(found);
311
+ }
312
+
313
+ /**
314
+ * Find many schemas by name and return their copies as an array in the same order.
315
+ * @param names
316
+ * @returns
317
+ */
318
+ findManySchemas(names: TSchemaName[]): GapSchema[] {
319
+ const schemas = Schema.getMany<TSchemaName, GapSchema>(names, this.network);
320
+ return schemas.map((s) => GapSchema.clone(s));
321
+ }
322
+
323
+ /**
324
+ * Get the multicall contract
325
+ * @param signer
326
+ */
327
+ static async getMulticall(signer: SignerOrProvider) {
328
+ const chain =
329
+ (await signer.provider.getNetwork()) || (signer.provider as any).network;
330
+ const network = Object.values(Networks).find(
331
+ (n) => +n.chainId === Number(chain.chainId)
332
+ );
333
+ if (!network)
334
+ throw new Error(`Network ${chain.name || chain.chainId} not supported.`);
335
+
336
+ const address = network.contracts.multicall;
337
+ return new ethers.Contract(address, MulticallABI, signer as any);
338
+ }
339
+
340
+ /**
341
+ * Get the multicall contract
342
+ * @param signer
343
+ */
344
+ static getProjectResolver(signer: SignerOrProvider, chainId?: number) {
345
+ const provider = chainId ? getWeb3Provider(chainId) : signer;
346
+ const network = Object.values(Networks).find(
347
+ (n) => +n.chainId === Number(chainId)
348
+ );
349
+ const address = network.contracts.projectResolver;
350
+ return new ethers.Contract(address, ProjectResolverABI, provider as any);
351
+ }
352
+
353
+ get schemas() {
354
+ return this._schemas;
355
+ }
356
+
357
+ /**
358
+ * Defined if the transactions will be gasless or not.
359
+ *
360
+ * In case of true, the transactions will be sent through [Gelato](https://gelato.network)
361
+ * and an API key is needed.
362
+ */
363
+ private static set gelatoOpts(gelatoOpts: GAPArgs['gelatoOpts']) {
364
+ if (typeof this._gelatoOpts === 'undefined') {
365
+ this._gelatoOpts = gelatoOpts;
366
+ } else {
367
+ throw new Error('Cannot change a readonly value gelatoOpts.');
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Defined if the transactions will be gasless or not.
373
+ *
374
+ * In case of true, the transactions will be sent through [Gelato](https://gelato.network)
375
+ * and an API key is needed.
376
+ */
377
+ static get gelatoOpts(): GAPArgs['gelatoOpts'] {
378
+ return this._gelatoOpts;
379
+ }
380
+
381
+ static set useGasLess(useGasLess: boolean) {
382
+ if (
383
+ useGasLess &&
384
+ !this._gelatoOpts?.apiKey &&
385
+ !this._gelatoOpts?.sponsorUrl &&
386
+ !this._gelatoOpts?.env_gelatoApiKey
387
+ ) {
388
+ throw new Error(
389
+ 'You must provide a `sponsorUrl` or an `apiKey` before using gasless transactions.'
390
+ );
391
+ }
392
+ this._gelatoOpts.useGasless = useGasLess;
393
+ }
394
+
395
+ static get remoteClient() {
396
+ return this.remoteStorage;
397
+ }
398
+ }
@@ -0,0 +1,90 @@
1
+ import { mapFilter } from '../utils';
2
+ import { IGapSchema, SchemaInterface, TNetwork, TSchemaName } from '../types';
3
+ import { Schema } from './Schema';
4
+ import { GAP } from './GAP';
5
+
6
+ /**
7
+ * Represents the GapSchema
8
+ * @extends Schema
9
+ */
10
+ export class GapSchema extends Schema implements IGapSchema {
11
+ public readonly name: TSchemaName;
12
+ public readonly references: TSchemaName;
13
+
14
+ constructor(
15
+ args: SchemaInterface<TSchemaName>,
16
+ gap: GAP,
17
+ strict = false,
18
+ ignoreSchema = false
19
+ ) {
20
+ super(args, gap, strict, ignoreSchema);
21
+
22
+ if (!ignoreSchema)
23
+ Schema.add(
24
+ gap.network,
25
+ new GapSchema(
26
+ {
27
+ name: args.name,
28
+ schema: args.schema.map((s) => ({ ...s })),
29
+ uid: args.uid,
30
+ references: args.references,
31
+ revocable: args.revocable,
32
+ },
33
+ gap,
34
+ strict,
35
+ true
36
+ )
37
+ );
38
+ }
39
+
40
+ /**
41
+ * Clones a schema without references to the original.
42
+ * @param schema
43
+ * @returns
44
+ */
45
+ static clone(schema: GapSchema) {
46
+ return new GapSchema(
47
+ {
48
+ name: schema.name,
49
+ schema: schema.schema.map((s) => ({ ...s })),
50
+ uid: schema.uid,
51
+ references: schema.references,
52
+ revocable: schema.revocable,
53
+ },
54
+ schema.gap,
55
+ false,
56
+ true
57
+ );
58
+ }
59
+
60
+ /**
61
+ * Returns a copy of the original schema with no pointers.
62
+ * @param name
63
+ * @returns
64
+ */
65
+ static find(name: TSchemaName, network: TNetwork): GapSchema {
66
+ const found = Schema.get<TSchemaName, GapSchema>(name, network);
67
+ return this.clone(found);
68
+ }
69
+
70
+ /**
71
+ * Find many schemas by name and return their copies as an array in the same order.
72
+ * @param names
73
+ * @returns
74
+ */
75
+ static findMany(names: TSchemaName[], network: TNetwork): GapSchema[] {
76
+ const schemas = Schema.getMany<TSchemaName, GapSchema>(names, network);
77
+ return schemas.map((s) => this.clone(s));
78
+ }
79
+
80
+ /**
81
+ * Get all schemas that references this schema.
82
+ */
83
+ get children() {
84
+ return mapFilter(
85
+ GapSchema.schemas[this.gap.network],
86
+ (s) => s.references === this.name || s.references === this.uid,
87
+ (s: Schema<TSchemaName>) => new GapSchema(s, s.gap, false, true)
88
+ );
89
+ }
90
+ }