@show-karma/karma-gap-sdk 0.4.15 → 0.4.16

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 (188) hide show
  1. package/.cursorrules +43 -0
  2. package/core/abi/AirdropNFT.json +1 -1
  3. package/core/abi/Allo.json +860 -860
  4. package/core/abi/AlloRegistry.json +578 -578
  5. package/core/abi/CommunityResolverABI.json +506 -506
  6. package/core/abi/Donations.json +251 -251
  7. package/core/abi/EAS.json +1 -1
  8. package/core/abi/MultiAttester.json +746 -746
  9. package/core/abi/ProjectResolver.json +574 -574
  10. package/core/abi/SchemaRegistry.json +1 -1
  11. package/core/abi/index.ts +21 -0
  12. package/core/class/AllGapSchemas.ts +21 -0
  13. package/core/class/Attestation.ts +429 -0
  14. package/core/class/Fetcher.ts +224 -0
  15. package/core/class/GAP.ts +481 -0
  16. package/core/class/GapSchema.ts +93 -0
  17. package/core/class/Gelato/{Gelato.js → Gelato.ts} +23 -0
  18. package/core/class/GrantProgramRegistry/Allo.ts +188 -0
  19. package/core/class/GrantProgramRegistry/AlloRegistry.ts +101 -0
  20. package/core/class/GraphQL/AxiosGQL.ts +29 -0
  21. package/core/class/GraphQL/EASClient.ts +34 -0
  22. package/core/class/GraphQL/GapEasClient.ts +869 -0
  23. package/core/class/Schema.ts +659 -0
  24. package/core/class/SchemaError.ts +42 -0
  25. package/core/class/contract/GapContract.ts +457 -0
  26. package/core/class/entities/Community.ts +148 -0
  27. package/core/class/entities/ContributorProfile.ts +108 -0
  28. package/core/class/entities/Grant.ts +321 -0
  29. package/core/class/entities/GrantUpdate.ts +187 -0
  30. package/core/class/entities/MemberOf.ts +52 -0
  31. package/core/class/entities/Milestone.ts +898 -0
  32. package/core/class/entities/Project.ts +672 -0
  33. package/core/class/entities/ProjectImpact.ts +170 -0
  34. package/core/class/entities/ProjectMilestone.ts +254 -0
  35. package/core/class/entities/ProjectPointer.ts +39 -0
  36. package/core/class/entities/ProjectUpdate.ts +176 -0
  37. package/core/class/entities/Track.ts +32 -0
  38. package/core/class/karma-indexer/GapIndexerClient.ts +383 -0
  39. package/core/class/karma-indexer/api/GapIndexerApi.ts +446 -0
  40. package/core/class/karma-indexer/api/types.ts +313 -0
  41. package/core/class/remote-storage/IpfsStorage.ts +76 -0
  42. package/core/class/remote-storage/RemoteStorage.ts +65 -0
  43. package/core/class/types/allo.ts +93 -0
  44. package/core/class/types/attestations.ts +223 -0
  45. package/core/consts.ts +775 -0
  46. package/core/scripts/create-grant.ts +102 -0
  47. package/core/scripts/create-program.ts +43 -0
  48. package/core/scripts/create-schemas.ts +65 -0
  49. package/core/scripts/deploy.ts +65 -0
  50. package/core/scripts/index.ts +1 -0
  51. package/core/scripts/milestone-multi-grants.ts +125 -0
  52. package/core/shared/types.ts +13 -0
  53. package/core/types.ts +224 -0
  54. package/core/utils/gelato/send-gelato-txn.ts +114 -0
  55. package/core/utils/gelato/sponsor-handler.ts +77 -0
  56. package/core/utils/gelato/watch-gelato-txn.ts +67 -0
  57. package/core/utils/get-date.ts +3 -0
  58. package/core/utils/get-ipfs-data.ts +13 -0
  59. package/core/utils/get-web3-provider.ts +18 -0
  60. package/core/utils/gql-queries.ts +133 -0
  61. package/core/utils/map-filter.ts +21 -0
  62. package/core/utils/serialize-bigint.ts +7 -0
  63. package/core/utils/to-unix.ts +18 -0
  64. package/create-community-example.ts +119 -0
  65. package/csv-upload/README.md +74 -0
  66. package/csv-upload/config.ts +41 -0
  67. package/csv-upload/example.csv +2 -0
  68. package/csv-upload/keys.example.json +8 -0
  69. package/csv-upload/scripts/run.ts +417 -0
  70. package/csv-upload/types.ts +39 -0
  71. package/docs/.gitkeep +0 -0
  72. package/docs/images/attestation-architecture.png +0 -0
  73. package/docs/images/dfd-get-projects.png +0 -0
  74. package/gap-schema.yaml +155 -0
  75. package/milestone-workflow-example.ts +353 -0
  76. package/package.json +45 -39
  77. package/readme.md +872 -0
  78. package/schemas/.gitkeep +0 -0
  79. package/schemas/GAP-schemas-1692135812877.json +33 -0
  80. package/test-file-indexer-api.ts +25 -0
  81. package/tsconfig.json +26 -0
  82. package/core/abi/index.d.ts +0 -1114
  83. package/core/abi/index.js +0 -26
  84. package/core/class/AllGapSchemas.d.ts +0 -9
  85. package/core/class/AllGapSchemas.js +0 -19
  86. package/core/class/Attestation.d.ts +0 -173
  87. package/core/class/Attestation.js +0 -333
  88. package/core/class/Fetcher.d.ts +0 -175
  89. package/core/class/Fetcher.js +0 -13
  90. package/core/class/GAP.d.ts +0 -254
  91. package/core/class/GAP.js +0 -289
  92. package/core/class/GapSchema.d.ts +0 -34
  93. package/core/class/GapSchema.js +0 -62
  94. package/core/class/GrantProgramRegistry/Allo.d.ts +0 -17
  95. package/core/class/GrantProgramRegistry/Allo.js +0 -137
  96. package/core/class/GrantProgramRegistry/AlloRegistry.d.ts +0 -15
  97. package/core/class/GrantProgramRegistry/AlloRegistry.js +0 -70
  98. package/core/class/GraphQL/AxiosGQL.d.ts +0 -6
  99. package/core/class/GraphQL/AxiosGQL.js +0 -25
  100. package/core/class/GraphQL/EASClient.d.ts +0 -16
  101. package/core/class/GraphQL/EASClient.js +0 -26
  102. package/core/class/GraphQL/GapEasClient.d.ts +0 -71
  103. package/core/class/GraphQL/GapEasClient.js +0 -451
  104. package/core/class/GraphQL/index.js +0 -19
  105. package/core/class/Schema.d.ts +0 -233
  106. package/core/class/Schema.js +0 -488
  107. package/core/class/SchemaError.d.ts +0 -30
  108. package/core/class/SchemaError.js +0 -39
  109. package/core/class/contract/GapContract.d.ts +0 -102
  110. package/core/class/contract/GapContract.js +0 -285
  111. package/core/class/entities/Community.d.ts +0 -34
  112. package/core/class/entities/Community.js +0 -109
  113. package/core/class/entities/ContributorProfile.d.ts +0 -41
  114. package/core/class/entities/ContributorProfile.js +0 -69
  115. package/core/class/entities/Grant.d.ts +0 -54
  116. package/core/class/entities/Grant.js +0 -223
  117. package/core/class/entities/GrantUpdate.d.ts +0 -40
  118. package/core/class/entities/GrantUpdate.js +0 -114
  119. package/core/class/entities/MemberOf.d.ts +0 -11
  120. package/core/class/entities/MemberOf.js +0 -33
  121. package/core/class/entities/Milestone.d.ts +0 -168
  122. package/core/class/entities/Milestone.js +0 -657
  123. package/core/class/entities/Project.d.ts +0 -92
  124. package/core/class/entities/Project.js +0 -418
  125. package/core/class/entities/ProjectImpact.d.ts +0 -50
  126. package/core/class/entities/ProjectImpact.js +0 -112
  127. package/core/class/entities/ProjectMilestone.d.ts +0 -60
  128. package/core/class/entities/ProjectMilestone.js +0 -174
  129. package/core/class/entities/ProjectPointer.d.ts +0 -12
  130. package/core/class/entities/ProjectPointer.js +0 -22
  131. package/core/class/entities/ProjectUpdate.d.ts +0 -50
  132. package/core/class/entities/ProjectUpdate.js +0 -110
  133. package/core/class/entities/Track.d.ts +0 -16
  134. package/core/class/entities/Track.js +0 -21
  135. package/core/class/entities/index.js +0 -26
  136. package/core/class/index.js +0 -26
  137. package/core/class/karma-indexer/GapIndexerClient.d.ts +0 -66
  138. package/core/class/karma-indexer/GapIndexerClient.js +0 -207
  139. package/core/class/karma-indexer/api/GapIndexerApi.d.ts +0 -73
  140. package/core/class/karma-indexer/api/GapIndexerApi.js +0 -256
  141. package/core/class/karma-indexer/api/types.d.ts +0 -295
  142. package/core/class/karma-indexer/api/types.js +0 -2
  143. package/core/class/remote-storage/IpfsStorage.d.ts +0 -23
  144. package/core/class/remote-storage/IpfsStorage.js +0 -56
  145. package/core/class/remote-storage/RemoteStorage.d.ts +0 -41
  146. package/core/class/remote-storage/RemoteStorage.js +0 -38
  147. package/core/class/types/allo.d.ts +0 -78
  148. package/core/class/types/allo.js +0 -2
  149. package/core/class/types/attestations.d.ts +0 -168
  150. package/core/class/types/attestations.js +0 -66
  151. package/core/consts.d.ts +0 -48
  152. package/core/consts.js +0 -641
  153. package/core/index.js +0 -24
  154. package/core/shared/types.d.ts +0 -6
  155. package/core/shared/types.js +0 -2
  156. package/core/types.d.ts +0 -131
  157. package/core/types.js +0 -13
  158. package/core/utils/gelato/index.js +0 -19
  159. package/core/utils/gelato/send-gelato-txn.d.ts +0 -55
  160. package/core/utils/gelato/send-gelato-txn.js +0 -100
  161. package/core/utils/gelato/sponsor-handler.d.ts +0 -9
  162. package/core/utils/gelato/sponsor-handler.js +0 -60
  163. package/core/utils/gelato/watch-gelato-txn.d.ts +0 -7
  164. package/core/utils/gelato/watch-gelato-txn.js +0 -63
  165. package/core/utils/get-date.d.ts +0 -1
  166. package/core/utils/get-date.js +0 -7
  167. package/core/utils/get-ipfs-data.d.ts +0 -1
  168. package/core/utils/get-ipfs-data.js +0 -20
  169. package/core/utils/get-web3-provider.d.ts +0 -2
  170. package/core/utils/get-web3-provider.js +0 -18
  171. package/core/utils/gql-queries.d.ts +0 -12
  172. package/core/utils/gql-queries.js +0 -90
  173. package/core/utils/index.js +0 -23
  174. package/core/utils/map-filter.d.ts +0 -8
  175. package/core/utils/map-filter.js +0 -20
  176. package/core/utils/serialize-bigint.d.ts +0 -1
  177. package/core/utils/serialize-bigint.js +0 -8
  178. package/core/utils/to-unix.d.ts +0 -1
  179. package/core/utils/to-unix.js +0 -25
  180. package/index.js +0 -17
  181. /package/core/class/GraphQL/{index.d.ts → index.ts} +0 -0
  182. /package/core/class/entities/{index.d.ts → index.ts} +0 -0
  183. /package/core/class/{index.d.ts → index.ts} +0 -0
  184. /package/core/{index.d.ts → index.ts} +0 -0
  185. /package/core/utils/gelato/{index.d.ts → index.ts} +0 -0
  186. /package/core/utils/{index.d.ts → index.ts} +0 -0
  187. /package/{core/class/Gelato/Gelato.d.ts → csv-upload/.gitkeep} +0 -0
  188. /package/{index.d.ts → index.ts} +0 -0
@@ -0,0 +1,898 @@
1
+ import { Transaction } from "ethers";
2
+ import { chainIdToNetwork } from "../../consts";
3
+ import {
4
+ Hex,
5
+ MultiAttestPayload,
6
+ MultiRevokeArgs,
7
+ SignerOrProvider,
8
+ TNetwork,
9
+ } from "../../types";
10
+ import { AllGapSchemas } from "../AllGapSchemas";
11
+ import { Attestation } from "../Attestation";
12
+ import { GAP } from "../GAP";
13
+ import { GapSchema } from "../GapSchema";
14
+ import { AttestationError } from "../SchemaError";
15
+ import { GapContract } from "../contract/GapContract";
16
+ import { IMilestoneResponse } from "../karma-indexer/api/types";
17
+ import {
18
+ AttestationWithTx,
19
+ MilestoneCompleted,
20
+ IMilestoneCompleted,
21
+ } from "../types/attestations";
22
+
23
+ interface _Milestone extends Milestone {}
24
+
25
+ export interface IMilestone {
26
+ title: string;
27
+ startsAt?: number;
28
+ endsAt: number;
29
+ description: string;
30
+ type?: string;
31
+ priority?: number;
32
+ }
33
+
34
+ /**
35
+ * Milestone class represents a milestone that can be attested to one or multiple grants.
36
+ *
37
+ * It provides methods to:
38
+ * - Create, complete, approve, reject, and verify milestones
39
+ * - Attest a milestone to a single grant
40
+ * - Attest a milestone to multiple grants in a single transaction
41
+ * - Complete, approve, and verify milestones across multiple grants
42
+ * - Revoke multiple milestone attestations at once
43
+ */
44
+ export class Milestone extends Attestation<IMilestone> implements IMilestone {
45
+ title: string;
46
+ startsAt?: number;
47
+ endsAt: number;
48
+ description: string;
49
+ completed: MilestoneCompleted;
50
+ approved: MilestoneCompleted;
51
+ rejected: MilestoneCompleted;
52
+ verified: MilestoneCompleted[] = [];
53
+ type = "milestone";
54
+ priority?: number;
55
+ /**
56
+ * Approves this milestone. If the milestone is not completed or already approved,
57
+ * it will throw an error.
58
+ * @param signer
59
+ * @param reason
60
+ */
61
+ async approve(
62
+ signer: SignerOrProvider,
63
+ data?: IMilestoneCompleted,
64
+ callback?: Function
65
+ ) {
66
+ if (!this.completed)
67
+ throw new AttestationError("ATTEST_ERROR", "Milestone is not completed");
68
+
69
+ const schema = this.schema.gap.findSchema("MilestoneCompleted");
70
+ if (this.schema.isJsonSchema()) {
71
+ schema.setValue(
72
+ "json",
73
+ JSON.stringify({
74
+ type: "approved",
75
+ ...data,
76
+ })
77
+ );
78
+ } else {
79
+ schema.setValue("type", "approved");
80
+ schema.setValue("reason", data?.reason || "");
81
+ schema.setValue("proofOfWork", data?.proofOfWork || "");
82
+ }
83
+ await this.attestStatus(signer, schema, callback);
84
+
85
+ this.approved = new MilestoneCompleted({
86
+ data: {
87
+ type: "approved",
88
+ reason: data?.reason || "",
89
+ },
90
+ refUID: this.uid,
91
+ schema: schema,
92
+ recipient: this.recipient,
93
+ });
94
+ }
95
+
96
+ /**
97
+ * Approves this milestone across multiple grants. If the milestones are not completed,
98
+ * it will throw an error.
99
+ * @param signer - The signer to use for attestation
100
+ * @param milestoneUIDs - Array of milestone UIDs to approve
101
+ * @param data - Optional approval data
102
+ * @param callback - Optional callback function for status updates
103
+ * @returns Promise with transaction and UIDs
104
+ */
105
+ async approveMultipleGrants(
106
+ signer: SignerOrProvider,
107
+ milestoneUIDs: Hex[],
108
+ data?: IMilestoneCompleted,
109
+ callback?: Function
110
+ ): Promise<AttestationWithTx> {
111
+ // Validate that all milestones are completed
112
+ if (!this.completed)
113
+ throw new AttestationError("ATTEST_ERROR", "Milestone is not completed");
114
+
115
+ const schema = this.schema.gap.findSchema("MilestoneCompleted");
116
+
117
+ if (this.schema.isJsonSchema()) {
118
+ schema.setValue(
119
+ "json",
120
+ JSON.stringify({
121
+ type: "approved",
122
+ ...data,
123
+ })
124
+ );
125
+ } else {
126
+ schema.setValue("type", "approved");
127
+ schema.setValue("reason", data?.reason || "");
128
+ schema.setValue("proofOfWork", data?.proofOfWork || "");
129
+ }
130
+
131
+ // Create approval attestations for each milestone
132
+ const approvalPayloads: MultiAttestPayload = [];
133
+
134
+ for (const milestoneUID of milestoneUIDs) {
135
+ const approved = new MilestoneCompleted({
136
+ data: {
137
+ type: "approved",
138
+ ...data,
139
+ },
140
+ refUID: milestoneUID,
141
+ schema,
142
+ recipient: this.recipient,
143
+ });
144
+
145
+ // Add approval to the payload
146
+ approvalPayloads.push([
147
+ approved,
148
+ await approved.payloadFor(0), // Index doesn't matter for approval
149
+ ]);
150
+ }
151
+
152
+ // Attest all approvals at once
153
+ const result = await GapContract.multiAttest(
154
+ signer,
155
+ approvalPayloads.map((p) => p[1]),
156
+ callback
157
+ );
158
+
159
+ // Save the first approval to this milestone instance
160
+ if (result.uids.length > 0) {
161
+ this.approved = new MilestoneCompleted({
162
+ data: {
163
+ type: "approved",
164
+ ...data,
165
+ },
166
+ refUID: milestoneUIDs[0],
167
+ uid: result.uids[0],
168
+ schema,
169
+ recipient: this.recipient,
170
+ });
171
+ }
172
+
173
+ return result;
174
+ }
175
+
176
+ /**
177
+ * Revokes the approved status of the milestone. If the milestone is not approved,
178
+ * it will throw an error.
179
+ * @param signer
180
+ */
181
+ async revokeApproval(signer: SignerOrProvider) {
182
+ if (!this.approved)
183
+ throw new AttestationError("ATTEST_ERROR", "Milestone is not approved");
184
+
185
+ await this.approved.schema.multiRevoke(signer, [
186
+ {
187
+ schemaId: this.completed.schema.uid,
188
+ uid: this.completed.uid,
189
+ },
190
+ ]);
191
+ }
192
+
193
+ /**
194
+ * Reject a completed milestone. If the milestone is not completed or already rejected,
195
+ * it will throw an error.
196
+ * @param signer
197
+ * @param reason
198
+ */
199
+ async reject(signer: SignerOrProvider, reason = "") {
200
+ if (!this.completed)
201
+ throw new AttestationError("ATTEST_ERROR", "Milestone is not completed");
202
+
203
+ const schema = this.schema.gap.findSchema("MilestoneCompleted");
204
+ schema.setValue("type", "rejected");
205
+ schema.setValue("reason", reason);
206
+ await this.attestStatus(signer, schema);
207
+
208
+ this.rejected = new MilestoneCompleted({
209
+ data: {
210
+ type: "rejected",
211
+ reason,
212
+ },
213
+ refUID: this.uid,
214
+ schema: schema,
215
+ recipient: this.recipient,
216
+ });
217
+ }
218
+
219
+ /**
220
+ * Revokes the rejected status of the milestone. If the milestone is not rejected,
221
+ * it will throw an error.
222
+ * @param signer
223
+ */
224
+ async revokeRejection(signer: SignerOrProvider) {
225
+ if (!this.rejected)
226
+ throw new AttestationError("ATTEST_ERROR", "Milestone is not rejected");
227
+
228
+ const { tx, uids } = await this.rejected.schema.multiRevoke(signer, [
229
+ {
230
+ schemaId: this.completed.schema.uid,
231
+ uid: this.completed.uid,
232
+ },
233
+ ]);
234
+ return { tx, uids };
235
+ }
236
+
237
+ /**
238
+ * Revokes multiple milestone attestations at once.
239
+ * This method can be used to revoke multiple milestone attestations in a single transaction.
240
+ *
241
+ * @param signer - The signer to use for revocation
242
+ * @param attestationsToRevoke - Array of objects containing schemaId and uid of attestations to revoke
243
+ * @param callback - Optional callback function for status updates
244
+ * @returns Promise with transaction and UIDs of revoked attestations
245
+ */
246
+ async revokeMultipleAttestations(
247
+ signer: SignerOrProvider,
248
+ attestationsToRevoke: MultiRevokeArgs[],
249
+ callback?: Function
250
+ ): Promise<AttestationWithTx> {
251
+ if (attestationsToRevoke.length === 0) {
252
+ throw new AttestationError(
253
+ "ATTEST_ERROR",
254
+ "No attestations specified for revocation"
255
+ );
256
+ }
257
+
258
+ // Group the attestations by schema ID
259
+ const groupedAttestations = attestationsToRevoke.reduce(
260
+ (acc, { schemaId, uid }) => {
261
+ if (!acc[schemaId]) {
262
+ acc[schemaId] = [];
263
+ }
264
+ acc[schemaId].push(uid);
265
+ return acc;
266
+ },
267
+ {} as Record<Hex, Hex[]>
268
+ );
269
+
270
+ // Convert to the format expected by GapContract.multiRevoke
271
+ const revocationPayload = Object.entries(groupedAttestations).map(
272
+ ([schemaId, uids]) => ({
273
+ schema: schemaId,
274
+ data: uids.map((uid) => ({
275
+ uid,
276
+ value: 0n, // EAS contract requires a value field as BigInt
277
+ })),
278
+ })
279
+ );
280
+
281
+ // Use GapContract.multiRevoke directly with the correct payload format
282
+ const result = await GapContract.multiRevoke(signer, revocationPayload);
283
+
284
+ if (callback) {
285
+ callback("confirmed");
286
+ }
287
+
288
+ return result;
289
+ }
290
+
291
+ /**
292
+ * Marks a milestone as completed. If the milestone is already completed,
293
+ * it will throw an error.
294
+ * @param signer
295
+ * @param reason
296
+ */
297
+ async complete(
298
+ signer: SignerOrProvider,
299
+ data?: IMilestoneCompleted,
300
+ callback?: Function
301
+ ): Promise<AttestationWithTx> {
302
+ const schema = this.schema.gap.findSchema("MilestoneCompleted");
303
+
304
+ if (this.schema.isJsonSchema()) {
305
+ schema.setValue(
306
+ "json",
307
+ JSON.stringify({
308
+ type: "completed",
309
+ ...data,
310
+ })
311
+ );
312
+ } else {
313
+ schema.setValue("type", "completed");
314
+ schema.setValue("reason", data?.reason || "");
315
+ schema.setValue("proofOfWork", data?.proofOfWork || "");
316
+ }
317
+ const { tx, uids } = await this.attestStatus(signer, schema, callback);
318
+ this.completed = new MilestoneCompleted({
319
+ data: {
320
+ type: "completed",
321
+ ...data,
322
+ },
323
+ refUID: this.uid,
324
+ schema,
325
+ recipient: this.recipient,
326
+ });
327
+ return { tx, uids };
328
+ }
329
+
330
+ /**
331
+ * Marks a milestone as completed across multiple grants. If the milestone is already completed,
332
+ * it will throw an error.
333
+ * @param signer - The signer to use for attestation
334
+ * @param grantIndices - Array of grant indices to attest this milestone to, or array of milestone UIDs
335
+ * @param data - Optional completion data
336
+ * @param callback - Optional callback function for status updates
337
+ * @returns Promise with transaction and UIDs
338
+ */
339
+ async completeForMultipleGrants(
340
+ signer: SignerOrProvider,
341
+ grantIndicesOrMilestoneUIDs: number[] | Hex[] = [0],
342
+ data?: IMilestoneCompleted,
343
+ callback?: Function
344
+ ): Promise<AttestationWithTx> {
345
+ // Check if we're dealing with UIDs instead of indices
346
+ const isUids =
347
+ grantIndicesOrMilestoneUIDs.length > 0 &&
348
+ typeof grantIndicesOrMilestoneUIDs[0] === "string";
349
+
350
+ let milestoneUIDs: Hex[] = [];
351
+
352
+ if (isUids) {
353
+ // We already have milestone UIDs
354
+ milestoneUIDs = grantIndicesOrMilestoneUIDs as Hex[];
355
+ } else {
356
+ // First attest the milestone to multiple grants if not already attested
357
+ const attestResult = await this.attestToMultipleGrants(
358
+ signer,
359
+ grantIndicesOrMilestoneUIDs as number[],
360
+ callback
361
+ );
362
+
363
+ milestoneUIDs = attestResult.uids;
364
+ }
365
+
366
+ // Now complete the milestone for each attested milestone
367
+ const schema = this.schema.gap.findSchema("MilestoneCompleted");
368
+
369
+ if (this.schema.isJsonSchema()) {
370
+ schema.setValue(
371
+ "json",
372
+ JSON.stringify({
373
+ type: "completed",
374
+ ...data,
375
+ })
376
+ );
377
+ } else {
378
+ schema.setValue("type", "completed");
379
+ schema.setValue("reason", data?.reason || "");
380
+ schema.setValue("proofOfWork", data?.proofOfWork || "");
381
+ }
382
+
383
+ // Create completion attestations for each milestone
384
+ const completionPayloads: MultiAttestPayload = [];
385
+
386
+ for (let i = 0; i < milestoneUIDs.length; i++) {
387
+ const milestoneUID = milestoneUIDs[i];
388
+
389
+ // Only set this.uid if we're dealing with indices and need to update the current milestone
390
+ if (!isUids) {
391
+ this.uid = milestoneUID; // Set the current UID to the milestone being completed
392
+ }
393
+
394
+ const completed = new MilestoneCompleted({
395
+ data: {
396
+ type: "completed",
397
+ ...data,
398
+ },
399
+ refUID: milestoneUID,
400
+ schema,
401
+ recipient: this.recipient,
402
+ });
403
+
404
+ // Add completed status to the payload
405
+ completionPayloads.push([completed, await completed.payloadFor(i)]);
406
+ }
407
+
408
+ // Attest all completions at once
409
+ const completionResult = await GapContract.multiAttest(
410
+ signer,
411
+ completionPayloads.map((p) => p[1]),
412
+ callback
413
+ );
414
+
415
+ // Save the first completion to this milestone instance
416
+ if (completionResult.uids.length > 0 && !isUids) {
417
+ this.completed = new MilestoneCompleted({
418
+ data: {
419
+ type: "completed",
420
+ ...data,
421
+ },
422
+ refUID: milestoneUIDs[0],
423
+ uid: completionResult.uids[0],
424
+ schema,
425
+ recipient: this.recipient,
426
+ });
427
+ }
428
+
429
+ return isUids
430
+ ? completionResult // If we're using UIDs directly, just return completion result
431
+ : {
432
+ tx: [
433
+ ...(milestoneUIDs.length ? [{ hash: "" } as Transaction] : []),
434
+ ...completionResult.tx,
435
+ ],
436
+ uids: [...milestoneUIDs, ...completionResult.uids],
437
+ };
438
+ }
439
+
440
+ /**
441
+ * Revokes the completed status of the milestone. If the milestone is not completed,
442
+ * it will throw an error.
443
+ * @param signer
444
+ */
445
+ async revokeCompletion(signer: SignerOrProvider, callback?: Function) {
446
+ if (!this.completed)
447
+ throw new AttestationError("ATTEST_ERROR", "Milestone is not completed");
448
+
449
+ const { tx, uids } = await this.completed.schema.multiRevoke(
450
+ signer,
451
+ [
452
+ {
453
+ schemaId: this.completed.schema.uid,
454
+ uid: this.completed.uid,
455
+ },
456
+ ],
457
+ callback
458
+ );
459
+ return { tx, uids };
460
+ }
461
+
462
+ /**
463
+ * Creates the payload for a multi-attestation.
464
+ *
465
+ * > if Current payload is set, it'll be used as the base payload
466
+ * and the project should refer to an index of the current payload,
467
+ * usually the community position.
468
+ *
469
+ * @param payload
470
+ * @param grantIdx
471
+ */
472
+ async multiAttestPayload(
473
+ currentPayload: MultiAttestPayload = [],
474
+ grantIdx = 0
475
+ ) {
476
+ this.assertPayload();
477
+ const payload = [...currentPayload];
478
+ const milestoneIdx =
479
+ payload.push([this, await this.payloadFor(grantIdx)]) - 1;
480
+ if (this.completed) {
481
+ payload.push([
482
+ this.completed,
483
+ await this.completed.payloadFor(milestoneIdx),
484
+ ]);
485
+ }
486
+ if (this.verified.length > 0) {
487
+ await Promise.all(
488
+ this.verified.map(async (m) => {
489
+ const payloadForMilestone = await m.payloadFor(milestoneIdx);
490
+ if (Array.isArray(payloadForMilestone)) {
491
+ payloadForMilestone.forEach((item) => payload.push(item));
492
+ }
493
+ })
494
+ );
495
+ }
496
+ return payload.slice(currentPayload.length, payload.length);
497
+ }
498
+
499
+ /**
500
+ * Creates the payload for a multi-attestation across multiple grants.
501
+ *
502
+ * This method allows for the same milestone to be attested to multiple grants
503
+ * in a single transaction.
504
+ *
505
+ * @param currentPayload - Current payload to append to
506
+ * @param grantIndices - Array of grant indices to attest this milestone to
507
+ * @returns The multi-attest payload with all grant attestations
508
+ */
509
+ async multiGrantAttestPayload(
510
+ currentPayload: MultiAttestPayload = [],
511
+ grantIndices: number[] = [0]
512
+ ) {
513
+ this.assertPayload();
514
+ const payload = [...currentPayload];
515
+ const milestoneIndices: number[] = [];
516
+
517
+ // Create milestone attestation for each grant
518
+ for (const grantIdx of grantIndices) {
519
+ const milestoneIdx =
520
+ payload.push([this, await this.payloadFor(grantIdx)]) - 1;
521
+ milestoneIndices.push(milestoneIdx);
522
+ }
523
+
524
+ // Add completed status if exists for each milestone
525
+ if (this.completed) {
526
+ for (const milestoneIdx of milestoneIndices) {
527
+ payload.push([
528
+ this.completed,
529
+ await this.completed.payloadFor(milestoneIdx),
530
+ ]);
531
+ }
532
+ }
533
+
534
+ // Add verifications if exist for each milestone
535
+ if (this.verified.length > 0) {
536
+ for (const milestoneIdx of milestoneIndices) {
537
+ await Promise.all(
538
+ this.verified.map(async (m) => {
539
+ const payloadForMilestone = await m.payloadFor(milestoneIdx);
540
+ if (Array.isArray(payloadForMilestone)) {
541
+ payloadForMilestone.forEach((item) => payload.push(item));
542
+ }
543
+ })
544
+ );
545
+ }
546
+ }
547
+
548
+ return payload.slice(currentPayload.length, payload.length);
549
+ }
550
+
551
+ /**
552
+ * Attests this milestone to multiple grants in a single transaction.
553
+ *
554
+ * @param signer - The signer to use for attestation
555
+ * @param grantIndices - Array of grant indices to attest this milestone to, or array of grant UIDs
556
+ * @param callback - Optional callback function for status updates
557
+ * @returns Promise with transaction and UIDs
558
+ */
559
+ async attestToMultipleGrants(
560
+ signer: SignerOrProvider,
561
+ grantIndices: number[] | Hex[] = [0],
562
+ callback?: Function
563
+ ): Promise<AttestationWithTx> {
564
+ this.assertPayload();
565
+
566
+ // Check if we're dealing with Hex UIDs instead of indices
567
+ const isUids =
568
+ grantIndices.length > 0 && typeof grantIndices[0] === "string";
569
+
570
+ if (isUids) {
571
+ // Direct approach - create individual milestone instances for each grant UID
572
+ const grantUIDs = grantIndices as Hex[];
573
+ const allPayloads: any[] = [];
574
+
575
+ for (const grantUID of grantUIDs) {
576
+ // Create a new milestone for each grant with direct reference
577
+ const grantMilestone = new Milestone({
578
+ schema: this.schema,
579
+ recipient: this.recipient,
580
+ data: this.data,
581
+ refUID: grantUID,
582
+ });
583
+
584
+ // Generate payload for this grant
585
+ const payload = await grantMilestone.multiAttestPayload();
586
+ // Add each item from payload to allPayloads
587
+ payload.forEach((item) => allPayloads.push(item));
588
+ }
589
+
590
+ // Attest all milestones in a single transaction
591
+ const result = await GapContract.multiAttest(
592
+ signer,
593
+ allPayloads.map((p) => p[1]),
594
+ callback
595
+ );
596
+
597
+ return result;
598
+ } else {
599
+ // Original implementation using grantIndices
600
+ const payload = await this.multiGrantAttestPayload(
601
+ [],
602
+ grantIndices as number[]
603
+ );
604
+
605
+ const { uids, tx } = await GapContract.multiAttest(
606
+ signer,
607
+ payload.map((p) => p[1]),
608
+ callback
609
+ );
610
+
611
+ if (Array.isArray(uids)) {
612
+ uids.forEach((uid, index) => {
613
+ payload[index][0].uid = uid;
614
+ });
615
+ }
616
+
617
+ return { tx, uids };
618
+ }
619
+ }
620
+
621
+ /**
622
+ * @inheritdoc
623
+ */
624
+ async attest(
625
+ signer: SignerOrProvider,
626
+ callback?: Function
627
+ ): Promise<AttestationWithTx> {
628
+ this.assertPayload();
629
+ const payload = await this.multiAttestPayload();
630
+
631
+ const { uids, tx } = await GapContract.multiAttest(
632
+ signer,
633
+ payload.map((p) => p[1]),
634
+ callback
635
+ );
636
+ if (Array.isArray(uids)) {
637
+ uids.forEach((uid, index) => {
638
+ payload[index][0].uid = uid;
639
+ });
640
+ }
641
+ console.log(uids);
642
+
643
+ return { tx, uids };
644
+ }
645
+
646
+ /**
647
+ * Attest the status of the milestone as approved, rejected or completed.
648
+ */
649
+ private async attestStatus(
650
+ signer: SignerOrProvider,
651
+ schema: GapSchema,
652
+ callback?: Function
653
+ ): Promise<AttestationWithTx> {
654
+ const eas = this.schema.gap.eas.connect(signer);
655
+ try {
656
+ if (callback) callback("preparing");
657
+ const tx = await eas.attest({
658
+ schema: schema.uid,
659
+ data: {
660
+ recipient: this.recipient,
661
+ data: schema.encode(),
662
+ refUID: this.uid,
663
+ expirationTime: 0n,
664
+ revocable: schema.revocable,
665
+ },
666
+ });
667
+
668
+ if (callback) callback("pending");
669
+ const uid = await tx.wait();
670
+ if (callback) callback("confirmed");
671
+ console.log(uid);
672
+
673
+ return {
674
+ tx: [
675
+ {
676
+ hash: tx.tx.hash as Hex,
677
+ } as Transaction,
678
+ ],
679
+ uids: [uid as `0x${string}`],
680
+ };
681
+ } catch (error: any) {
682
+ console.error(error);
683
+ throw new AttestationError("ATTEST_ERROR", error.message, error);
684
+ }
685
+ }
686
+
687
+ static from(
688
+ attestations: IMilestoneResponse[],
689
+ network: TNetwork
690
+ ): Milestone[] {
691
+ return attestations.map((attestation) => {
692
+ const milestone = new Milestone({
693
+ ...attestation,
694
+ data: {
695
+ ...attestation.data,
696
+ },
697
+ schema: new AllGapSchemas().findSchema(
698
+ "Milestone",
699
+ chainIdToNetwork[attestation.chainID] as TNetwork
700
+ ),
701
+ chainID: attestation.chainID,
702
+ });
703
+
704
+ if (attestation.completed) {
705
+ milestone.completed = new MilestoneCompleted({
706
+ ...attestation.completed,
707
+ data: {
708
+ ...attestation.completed.data,
709
+ },
710
+ schema: new AllGapSchemas().findSchema(
711
+ "MilestoneCompleted",
712
+ chainIdToNetwork[attestation.chainID] as TNetwork
713
+ ),
714
+ chainID: attestation.chainID,
715
+ });
716
+ }
717
+
718
+ if (attestation.approved) {
719
+ milestone.approved = new MilestoneCompleted({
720
+ ...attestation.approved,
721
+ data: {
722
+ ...attestation.completed.data,
723
+ },
724
+ schema: new AllGapSchemas().findSchema(
725
+ "MilestoneCompleted",
726
+ chainIdToNetwork[attestation.chainID] as TNetwork
727
+ ),
728
+ chainID: attestation.chainID,
729
+ });
730
+ }
731
+
732
+ if (attestation.rejected) {
733
+ milestone.rejected = new MilestoneCompleted({
734
+ ...attestation.rejected,
735
+ data: {
736
+ ...attestation.completed.data,
737
+ },
738
+ schema: new AllGapSchemas().findSchema(
739
+ "MilestoneCompleted",
740
+ chainIdToNetwork[attestation.chainID] as TNetwork
741
+ ),
742
+ chainID: attestation.chainID,
743
+ });
744
+ }
745
+
746
+ if (attestation.verified?.length > 0) {
747
+ milestone.verified = attestation.verified.map(
748
+ (m) =>
749
+ new MilestoneCompleted({
750
+ ...m,
751
+ data: {
752
+ ...m.data,
753
+ },
754
+ schema: new AllGapSchemas().findSchema(
755
+ "MilestoneCompleted",
756
+ chainIdToNetwork[attestation.chainID] as TNetwork
757
+ ),
758
+ chainID: attestation.chainID,
759
+ })
760
+ );
761
+ }
762
+
763
+ return milestone;
764
+ });
765
+ }
766
+
767
+ /**
768
+ * Verify this milestone. If the milestone is not completed or already verified,
769
+ * it will throw an error.
770
+ * @param signer
771
+ * @param reason
772
+ */
773
+ async verify(
774
+ signer: SignerOrProvider,
775
+ data?: IMilestoneCompleted,
776
+ callback?: Function
777
+ ): Promise<AttestationWithTx> {
778
+ console.log("Verifying");
779
+ if (!this.completed)
780
+ throw new AttestationError("ATTEST_ERROR", "Milestone is not completed");
781
+
782
+ const schema = this.schema.gap.findSchema("MilestoneCompleted");
783
+
784
+ if (this.schema.isJsonSchema()) {
785
+ schema.setValue(
786
+ "json",
787
+ JSON.stringify({
788
+ type: "verified",
789
+ ...data,
790
+ })
791
+ );
792
+ } else {
793
+ schema.setValue("type", "verified");
794
+ schema.setValue("reason", data?.reason || "");
795
+ schema.setValue("proofOfWork", data?.proofOfWork || "");
796
+ }
797
+ console.log("Before attestStatus");
798
+ const { tx, uids } = await this.attestStatus(signer, schema, callback);
799
+ console.log("After attestStatus");
800
+
801
+ this.verified.push(
802
+ new MilestoneCompleted({
803
+ data: {
804
+ type: "verified",
805
+ ...data,
806
+ },
807
+ refUID: this.uid,
808
+ schema: schema,
809
+ recipient: this.recipient,
810
+ })
811
+ );
812
+ return { tx, uids };
813
+ }
814
+
815
+ /**
816
+ * Verifies this milestone across multiple grants. If the milestones are not completed,
817
+ * it will throw an error.
818
+ * @param signer - The signer to use for attestation
819
+ * @param milestoneUIDs - Array of milestone UIDs to verify
820
+ * @param data - Optional verification data
821
+ * @param callback - Optional callback function for status updates
822
+ * @returns Promise with transaction and UIDs
823
+ */
824
+ async verifyMultipleGrants(
825
+ signer: SignerOrProvider,
826
+ milestoneUIDs: Hex[],
827
+ data?: IMilestoneCompleted,
828
+ callback?: Function
829
+ ): Promise<AttestationWithTx> {
830
+ // Validate that all milestones are completed
831
+ if (!this.completed)
832
+ throw new AttestationError("ATTEST_ERROR", "Milestone is not completed");
833
+
834
+ const schema = this.schema.gap.findSchema("MilestoneCompleted");
835
+
836
+ if (this.schema.isJsonSchema()) {
837
+ schema.setValue(
838
+ "json",
839
+ JSON.stringify({
840
+ type: "verified",
841
+ ...data,
842
+ })
843
+ );
844
+ } else {
845
+ schema.setValue("type", "verified");
846
+ schema.setValue("reason", data?.reason || "");
847
+ schema.setValue("proofOfWork", data?.proofOfWork || "");
848
+ }
849
+
850
+ // Create verification attestations for each milestone
851
+ const verificationPayloads: MultiAttestPayload = [];
852
+
853
+ for (const milestoneUID of milestoneUIDs) {
854
+ const verified = new MilestoneCompleted({
855
+ data: {
856
+ type: "verified",
857
+ ...data,
858
+ },
859
+ refUID: milestoneUID,
860
+ schema,
861
+ recipient: this.recipient,
862
+ });
863
+
864
+ // Add verification to the payload
865
+ verificationPayloads.push([
866
+ verified,
867
+ await verified.payloadFor(0), // Index doesn't matter for verification
868
+ ]);
869
+ }
870
+
871
+ // Attest all verifications at once
872
+ const result = await GapContract.multiAttest(
873
+ signer,
874
+ verificationPayloads.map((p) => p[1]),
875
+ callback
876
+ );
877
+
878
+ // Save the verifications to this milestone instance
879
+ if (result.uids.length > 0) {
880
+ for (let i = 0; i < result.uids.length; i++) {
881
+ this.verified.push(
882
+ new MilestoneCompleted({
883
+ data: {
884
+ type: "verified",
885
+ ...data,
886
+ },
887
+ refUID: milestoneUIDs[i],
888
+ uid: result.uids[i],
889
+ schema,
890
+ recipient: this.recipient,
891
+ })
892
+ );
893
+ }
894
+ }
895
+
896
+ return result;
897
+ }
898
+ }