@dorafactory/maci-sdk 0.0.20 → 0.0.21

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dorafactory/maci-sdk",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "description": "SDK for interacting with maci",
5
5
  "keywords": [
6
6
  "maci",
@@ -27,7 +27,11 @@ import {
27
27
  CreateMaciRoundParams,
28
28
  CreateOracleMaciRoundParams,
29
29
  } from './types';
30
- import { getCircuitType, getContractParams } from './utils';
30
+ import {
31
+ getAMaciRoundCircuitFee,
32
+ getCircuitType,
33
+ getContractParams,
34
+ } from './utils';
31
35
  import { QTR_LIB } from './vars';
32
36
  import { MaciRoundType, MaciCertSystemType } from '../../types';
33
37
  import { decompressPublicKey } from '../../utils';
@@ -82,26 +86,33 @@ export class Contract {
82
86
  contractAddress: this.registryAddress,
83
87
  });
84
88
 
89
+ const requiredFee = getAMaciRoundCircuitFee(maxVoter, maxOption);
90
+
85
91
  preDeactivateRoot = preDeactivateRoot || '0';
86
- const res = await client.createRound({
87
- operator,
88
- preDeactivateRoot,
89
- voiceCreditAmount,
90
- whitelist,
91
- roundInfo: {
92
- title,
93
- description: description || '',
94
- link: link || '',
95
- },
96
- votingTime: {
97
- start_time,
98
- end_time,
92
+ const res = await client.createRound(
93
+ {
94
+ operator,
95
+ preDeactivateRoot,
96
+ voiceCreditAmount,
97
+ whitelist,
98
+ roundInfo: {
99
+ title,
100
+ description: description || '',
101
+ link: link || '',
102
+ },
103
+ votingTime: {
104
+ start_time,
105
+ end_time,
106
+ },
107
+ maxVoter: maxVoter.toString(),
108
+ maxOption: maxOption.toString(),
109
+ certificationSystem: '0',
110
+ circuitType,
99
111
  },
100
- maxVoter,
101
- maxOption,
102
- certificationSystem: '0',
103
- circuitType,
104
- });
112
+ 'auto',
113
+ undefined,
114
+ [requiredFee]
115
+ );
105
116
  let contractAddress = '';
106
117
  res.events.map((event) => {
107
118
  if (event.type === 'wasm') {
@@ -208,8 +219,8 @@ export class Contract {
208
219
  MaciRoundType.ORACLE_MACI,
209
220
  circuitType,
210
221
  MaciCertSystemType.GROTH16,
211
- '0',
212
- '0'
222
+ 0,
223
+ 0
213
224
  );
214
225
  const instantiateResponse = await client.instantiate(
215
226
  address,
@@ -18,8 +18,8 @@ export type CreateRoundParams = {
18
18
  };
19
19
 
20
20
  export type CreateAMaciRoundParams = {
21
- maxVoter: string;
22
- maxOption: string;
21
+ maxVoter: number;
22
+ maxOption: number;
23
23
  operator: string;
24
24
  whitelist: RegistryWhitelist;
25
25
  voiceCreditAmount: string;
@@ -27,8 +27,8 @@ export type CreateAMaciRoundParams = {
27
27
  } & CreateRoundParams;
28
28
 
29
29
  export type CreateMaciRoundParams = {
30
- maxVoter: string;
31
- maxOption: string;
30
+ maxVoter: number;
31
+ maxOption: number;
32
32
  operatorPubkey: string;
33
33
  whitelist: MaciWhitelist;
34
34
  certSystemType: MaciCertSystemType;
@@ -50,8 +50,8 @@ export function getContractParams(
50
50
  type: MaciRoundType,
51
51
  circuitType: MaciCircuitType,
52
52
  proofSystem: MaciCertSystemType,
53
- maxVoter: string,
54
- maxOption: string
53
+ maxVoter: number,
54
+ maxOption: number
55
55
  ) {
56
56
  let parameters: {
57
57
  state_tree_depth: string;
@@ -92,7 +92,7 @@ export function getContractParams(
92
92
  );
93
93
  }
94
94
 
95
- if (Number(maxVoter) <= 25 && Number(maxOption) <= 5) {
95
+ if (maxVoter <= 25 && maxOption <= 5) {
96
96
  // state_tree_depth: 2
97
97
  // vote_option_tree_depth: 1
98
98
  parameters = CIRCUIT_INFO['2-1-1-5'].parameter;
@@ -103,7 +103,7 @@ export function getContractParams(
103
103
  plonkProcessVkey = CIRCUIT_INFO['2-1-1-5']['plonk']?.process_vkey;
104
104
  plonkTallyVkey = CIRCUIT_INFO['2-1-1-5']['plonk']?.tally_vkey;
105
105
  }
106
- } else if (Number(maxVoter) <= 625 && Number(maxOption) <= 25) {
106
+ } else if (maxVoter <= 625 && maxOption <= 25) {
107
107
  // state_tree_depth: 4
108
108
  // vote_option_tree_depth: 2
109
109
  parameters = CIRCUIT_INFO['4-2-2-25'].parameter;
@@ -114,7 +114,7 @@ export function getContractParams(
114
114
  plonkProcessVkey = CIRCUIT_INFO['4-2-2-25']['plonk']?.process_vkey;
115
115
  plonkTallyVkey = CIRCUIT_INFO['4-2-2-25']['plonk']?.tally_vkey;
116
116
  }
117
- } else if (Number(maxVoter) <= 15625 && Number(maxOption) <= 125) {
117
+ } else if (maxVoter <= 15625 && maxOption <= 125) {
118
118
  // state_tree_depth: 6
119
119
  // vote_option_tree_depth: 3
120
120
  parameters = CIRCUIT_INFO['6-3-3-125'].parameter;
@@ -125,7 +125,7 @@ export function getContractParams(
125
125
  plonkProcessVkey = CIRCUIT_INFO['6-3-3-125']['plonk']?.process_vkey;
126
126
  plonkTallyVkey = CIRCUIT_INFO['6-3-3-125']['plonk']?.tally_vkey;
127
127
  }
128
- } else if (Number(maxVoter) <= 1953125 && Number(maxOption) <= 125) {
128
+ } else if (maxVoter <= 1953125 && maxOption <= 125) {
129
129
  // state_tree_depth: 9
130
130
  // vote_option_tree_depth: 3
131
131
  parameters = CIRCUIT_INFO['9-4-3-625'].parameter;
@@ -185,3 +185,23 @@ export function getContractParams(
185
185
  };
186
186
  }
187
187
  }
188
+
189
+ export function getAMaciRoundCircuitFee(maxVoter: number, maxOption: number) {
190
+ let requiredFee = {
191
+ denom: 'peaka',
192
+ amount: '0',
193
+ };
194
+ if (maxVoter <= 25 && maxOption <= 5) {
195
+ // state_tree_depth: 2
196
+ // vote_option_tree_depth: 1
197
+ requiredFee.amount = '50000000000000000000';
198
+ } else if (maxVoter <= 625 && maxOption <= 25) {
199
+ // state_tree_depth: 4
200
+ // vote_option_tree_depth: 2
201
+ requiredFee.amount = '100000000000000000000';
202
+ } else {
203
+ throw new Error('Number of voters or options is too large.');
204
+ }
205
+
206
+ return requiredFee;
207
+ }
@@ -3,6 +3,9 @@ export const ERROR = {
3
3
  ERROR_CIRCUIT_NOT_FOUND: 'ERROR_CIRCUIT_NOT_FOUND',
4
4
  ERROR_OPERATOR_INVALID_ADDRESS: 'ERROR_OPERATOR_INVALID_ADDRESS',
5
5
  ERROR_OPERATOR_NOT_FOUND: 'ERROR_OPERATOR_NOT_FOUND',
6
+ ERROR_OPERATOR_DELAY_HISTORY_NOT_FOUND:
7
+ 'ERROR_OPERATOR_DELAY_HISTORY_NOT_FOUND',
8
+ ERROR_QUERY_MISS_RATE_FAILED: 'ERROR_QUERY_MISS_RATE_FAILED',
6
9
  ERROR_OPERATORS_NOT_FOUND: 'ERROR_OPERATORS_NOT_FOUND',
7
10
  ERROR_PROOF_NOT_FOUND: 'ERROR_PROOF_NOT_FOUND',
8
11
  ERROR_ROUND_INVALID_ADDRESS: 'ERROR_ROUND_INVALID_ADDRESS',
@@ -10,6 +10,8 @@ import {
10
10
  CircuitsResponse,
11
11
  ProofResponse,
12
12
  SignUpEventsResponse,
13
+ OperatorDelayOperationsResponse,
14
+ MissRateResponse,
13
15
  } from '../../types';
14
16
  import { IndexerParams } from './types';
15
17
  import { Http } from '../http';
@@ -155,6 +157,25 @@ export class Indexer {
155
157
  return await this.operator.getOperatorByAddress(address);
156
158
  }
157
159
 
160
+ async getOperatorDelayOperationsByAddress(
161
+ address: string,
162
+ after: string,
163
+ limit?: number
164
+ ): Promise<OperatorDelayOperationsResponse> {
165
+ return await this.operator.getOperatorDelayOperationsByAddress(
166
+ address,
167
+ after,
168
+ limit
169
+ );
170
+ }
171
+
172
+ async queryMissRate(
173
+ address: string,
174
+ durationDay: number
175
+ ): Promise<MissRateResponse> {
176
+ return await this.operator.queryMissRate(address, durationDay);
177
+ }
178
+
158
179
  /**
159
180
  * @method getOperators
160
181
  * @description Get multiple operators.
@@ -253,6 +253,66 @@ export class MACI {
253
253
  return circuitType === '1';
254
254
  }
255
255
 
256
+ async queryRoundClaimable({
257
+ contractAddress,
258
+ }: {
259
+ contractAddress: string;
260
+ }): Promise<{
261
+ claimable: boolean | null;
262
+ balance: string | null;
263
+ }> {
264
+ try {
265
+ const roundInfo = await this.getRoundInfo({ contractAddress });
266
+
267
+ if (roundInfo.maciType !== 'aMACI') {
268
+ return {
269
+ claimable: null,
270
+ balance: null,
271
+ };
272
+ }
273
+
274
+ const votingEndTime = new Date(Number(roundInfo.votingEnd) / 10 ** 6);
275
+ const currentTime = new Date();
276
+ const threeDaysInMs = 3 * 24 * 60 * 60 * 1000;
277
+
278
+ if (currentTime.getTime() - votingEndTime.getTime() <= threeDaysInMs) {
279
+ return {
280
+ claimable: null,
281
+ balance: null,
282
+ };
283
+ }
284
+
285
+ const roundBalance = await this.indexer.balanceOf(contractAddress);
286
+ if (isErrorResponse(roundBalance)) {
287
+ throw new Error(
288
+ `Failed to query round balance: ${roundBalance.error.type} ${roundBalance.error.message}`
289
+ );
290
+ }
291
+
292
+ if (
293
+ roundBalance.data.balance &&
294
+ roundBalance.data.balance !== '0' &&
295
+ roundBalance.data.balance !== ''
296
+ ) {
297
+ return {
298
+ claimable: true,
299
+ balance: roundBalance.data.balance,
300
+ };
301
+ }
302
+
303
+ return {
304
+ claimable: false,
305
+ balance: roundBalance.data.balance,
306
+ };
307
+ } catch (error) {
308
+ console.error('Error in queryRoundClaimable:', error);
309
+ return {
310
+ claimable: null,
311
+ balance: null,
312
+ };
313
+ }
314
+ }
315
+
256
316
  async queryRoundGasStation({ contractAddress }: { contractAddress: string }) {
257
317
  const roundInfo = await this.getRoundInfo({ contractAddress });
258
318
 
@@ -5,6 +5,12 @@ import {
5
5
  RoundsCountGraphqlResponse,
6
6
  OperatorResponse,
7
7
  OperatorsResponse,
8
+ OperatorDelayOperationsResponse,
9
+ OperatorDelayOperationsGraphqlResponse,
10
+ MissRateResponse,
11
+ MissRateType,
12
+ TransactionsGraphqlResponse,
13
+ RoundsGraphqlResponse,
8
14
  } from '../../types';
9
15
  import { isValidAddress } from '../../utils';
10
16
  import { handleError, ErrorType } from '../errors';
@@ -131,6 +137,79 @@ export class Operator {
131
137
  }
132
138
  }
133
139
 
140
+ async getOperatorDelayOperationsByAddress(
141
+ address: string,
142
+ after: string,
143
+ limit?: number
144
+ ): Promise<OperatorDelayOperationsResponse> {
145
+ try {
146
+ if (!isValidAddress(address)) {
147
+ return {
148
+ code: 400,
149
+ error: {
150
+ message: 'Invalid operator address format',
151
+ type: ERROR.ERROR_OPERATOR_INVALID_ADDRESS,
152
+ },
153
+ };
154
+ }
155
+
156
+ const OPERATORS_QUERY = `query ($limit: Int, $after: Cursor) {
157
+ operatorDelayOperations(first: $limit, after: $after, filter: {operatorAddress: {equalTo: "${address}"}}, orderBy: [TIMESTAMP_DESC]) {
158
+ pageInfo {
159
+ endCursor
160
+ hasNextPage
161
+ }
162
+ totalCount
163
+ edges {
164
+ cursor
165
+ node {
166
+ blockHeight
167
+ delayProcessDmsgCount
168
+ delayDuration
169
+ delayReason
170
+ delayType
171
+ id
172
+ nodeId
173
+ operatorAddress
174
+ timestamp
175
+ roundAddress
176
+ }
177
+ }
178
+ }
179
+ }`;
180
+
181
+ const response =
182
+ await this.http.fetchGraphql<OperatorDelayOperationsGraphqlResponse>(
183
+ OPERATORS_QUERY,
184
+ after,
185
+ limit
186
+ );
187
+
188
+ if (
189
+ !response ||
190
+ !response.data ||
191
+ !response.data.operatorDelayOperations ||
192
+ !response.data.operatorDelayOperations.edges ||
193
+ response.data.operatorDelayOperations.edges.length === 0
194
+ ) {
195
+ return {
196
+ code: 404,
197
+ error: {
198
+ message: `No operatorDelayOperations found for address ${address}`,
199
+ type: ERROR.ERROR_OPERATOR_DELAY_HISTORY_NOT_FOUND,
200
+ },
201
+ };
202
+ }
203
+ const operator: OperatorDelayOperationsResponse = {
204
+ code: 200,
205
+ data: response.data,
206
+ };
207
+ return operator;
208
+ } catch (error) {
209
+ return handleError(error as ErrorType);
210
+ }
211
+ }
212
+
134
213
  async getOperators(
135
214
  after: string,
136
215
  limit?: number
@@ -260,4 +339,263 @@ export class Operator {
260
339
  return handleError(error as ErrorType);
261
340
  }
262
341
  }
342
+
343
+ async queryMissRate(
344
+ address: string,
345
+ durationDay: number
346
+ ): Promise<MissRateResponse> {
347
+ try {
348
+ const now = new Date();
349
+ const startTime = new Date(
350
+ now.getTime() - durationDay * 24 * 60 * 60 * 1000
351
+ );
352
+ const startTimestamp = Math.floor(startTime.getTime() / 1000);
353
+ const endNanosTimestamp = Math.floor(startTime.getTime() * 1000000);
354
+ const txTimestamp = Math.floor(startTime.getTime());
355
+
356
+ const QUERY = `query ($limit: Int, $after: Cursor) {
357
+ operatorDelayOperations(
358
+ first: $limit,
359
+ after: $after,
360
+ filter: {
361
+ operatorAddress: {equalTo: "${address}"},
362
+ timestamp: { greaterThanOrEqualTo: "${startTimestamp}" }
363
+ },
364
+ orderBy: [TIMESTAMP_DESC]
365
+ ) {
366
+ edges {
367
+ node {
368
+ blockHeight
369
+ delayProcessDmsgCount
370
+ delayDuration
371
+ delayReason
372
+ delayType
373
+ id
374
+ nodeId
375
+ operatorAddress
376
+ timestamp
377
+ roundAddress
378
+ }
379
+ }
380
+ }
381
+ }`;
382
+
383
+ const ROUNDS_QUERY = `query ($limit: Int, $after: Cursor) {
384
+ rounds(first: $limit, after: $after,
385
+ filter: {
386
+ operator: {equalTo: "${address}"},
387
+ votingEnd: { greaterThanOrEqualTo: "${endNanosTimestamp}" }
388
+ },
389
+ orderBy: [TIMESTAMP_DESC]
390
+ ){
391
+ pageInfo {
392
+ endCursor
393
+ hasNextPage
394
+ }
395
+ totalCount
396
+ edges {
397
+ node {
398
+ id
399
+ blockHeight
400
+ txHash
401
+ caller
402
+ admin
403
+ operator
404
+ contractAddress
405
+ circuitName
406
+ timestamp
407
+ votingStart
408
+ votingEnd
409
+ status
410
+ period
411
+ actionType
412
+ roundTitle
413
+ roundDescription
414
+ roundLink
415
+ coordinatorPubkeyX
416
+ coordinatorPubkeyY
417
+ voteOptionMap
418
+ results
419
+ allResult
420
+ gasStationEnable
421
+ totalGrant
422
+ baseGrant
423
+ totalBond
424
+ circuitType
425
+ circuitPower
426
+ certificationSystem
427
+ codeId
428
+ maciType
429
+ voiceCreditAmount
430
+ preDeactivateRoot
431
+ }
432
+ cursor
433
+ }
434
+ }
435
+ }
436
+ `;
437
+
438
+ const roundsResponse =
439
+ await this.http.fetchGraphql<RoundsGraphqlResponse>(
440
+ ROUNDS_QUERY,
441
+ '',
442
+ 9999
443
+ );
444
+ const roundContractAddresses =
445
+ roundsResponse?.data?.rounds?.edges?.map(
446
+ (edge) => edge.node.contractAddress
447
+ ) || [];
448
+
449
+ const TRANSACTIONS_QUERY = `query transactions($limit: Int, $after: Cursor) {
450
+ transactions(first: $limit, after: $after,
451
+ filter: {
452
+ timestamp: { greaterThanOrEqualTo: "${txTimestamp}" },
453
+ type: { equalTo: "op:procDeactivate" },
454
+ contractAddress: { in: ${JSON.stringify(roundContractAddresses)} }
455
+ },
456
+ orderBy: [TIMESTAMP_DESC]
457
+ ){
458
+ pageInfo {
459
+ endCursor
460
+ hasNextPage
461
+ }
462
+ totalCount
463
+ edges {
464
+ cursor
465
+ node {
466
+ id
467
+ blockHeight
468
+ txHash
469
+ timestamp
470
+ type
471
+ status
472
+ circuitName
473
+ fee
474
+ gasUsed
475
+ gasWanted
476
+ caller
477
+ contractAddress
478
+ }
479
+ }
480
+ }
481
+ }`;
482
+
483
+ const [delayResponse, transactionsResponse] = await Promise.all([
484
+ this.http.fetchGraphql<OperatorDelayOperationsGraphqlResponse>(
485
+ QUERY,
486
+ '',
487
+ 9999
488
+ ),
489
+ this.http.fetchGraphql<TransactionsGraphqlResponse>(
490
+ TRANSACTIONS_QUERY,
491
+ '',
492
+ 9999
493
+ ),
494
+ ]);
495
+
496
+ const dailyStats = new Map<string, MissRateType>();
497
+
498
+ const endDate = new Date();
499
+ for (let i = 0; i < durationDay; i++) {
500
+ const date = new Date(endDate.getTime() - i * 24 * 60 * 60 * 1000)
501
+ .toISOString()
502
+ .split('T')[0];
503
+ dailyStats.set(date, {
504
+ delayCount: 0,
505
+ deactivateDelay: {
506
+ count: 0,
507
+ dmsgCount: 0,
508
+ },
509
+ tallyDelay: {
510
+ count: 0,
511
+ },
512
+ totalDelayDuration: 0,
513
+ avgDelayDuration: 0,
514
+ tallyCount: 0,
515
+ deactivateCount: 0,
516
+ missRate: 0,
517
+ });
518
+ }
519
+
520
+ delayResponse.data.operatorDelayOperations.edges.forEach(({ node }) => {
521
+ const date = new Date(parseInt(node.timestamp) * 1000)
522
+ .toISOString()
523
+ .split('T')[0];
524
+
525
+ if (dailyStats.has(date)) {
526
+ const stats = dailyStats.get(date)!;
527
+ stats.delayCount++;
528
+ stats.totalDelayDuration += parseInt(node.delayDuration);
529
+ if (node.delayType === 'deactivate_delay') {
530
+ stats.deactivateDelay.count++;
531
+ stats.deactivateDelay.dmsgCount += node.delayProcessDmsgCount;
532
+ } else if (node.delayType === 'tally_delay') {
533
+ stats.tallyDelay.count++;
534
+ }
535
+ }
536
+ });
537
+
538
+ if (roundsResponse?.data?.rounds?.edges) {
539
+ roundsResponse.data.rounds.edges.forEach(({ node }) => {
540
+ const date = new Date(parseInt(node.votingEnd) / 1000000)
541
+ .toISOString()
542
+ .split('T')[0];
543
+ if (dailyStats.has(date)) {
544
+ const stats = dailyStats.get(date)!;
545
+ stats.tallyCount++;
546
+ }
547
+ });
548
+ }
549
+
550
+ if (transactionsResponse?.data?.transactions?.edges) {
551
+ transactionsResponse.data.transactions.edges.forEach(({ node }) => {
552
+ const date = new Date(parseInt(node.timestamp))
553
+ .toISOString()
554
+ .split('T')[0];
555
+ if (dailyStats.has(date)) {
556
+ const stats = dailyStats.get(date)!;
557
+ stats.deactivateCount++;
558
+ }
559
+ });
560
+ }
561
+
562
+ return {
563
+ code: 200,
564
+ data: {
565
+ missRate: Array.from(dailyStats.entries())
566
+ .map(([date, stats]) => ({
567
+ date,
568
+ delayCount: stats.delayCount,
569
+ deactivateDelay: stats.deactivateDelay,
570
+ tallyDelay: stats.tallyDelay,
571
+ totalDelayDuration: stats.totalDelayDuration,
572
+ avgDelayDuration:
573
+ stats.delayCount > 0
574
+ ? stats.totalDelayDuration / stats.delayCount
575
+ : 0,
576
+ tallyCount: stats.tallyCount,
577
+ deactivateCount: stats.deactivateCount,
578
+ missRate:
579
+ stats.deactivateCount + stats.tallyCount > 0
580
+ ? parseFloat(
581
+ (
582
+ (stats.deactivateDelay.count + stats.tallyDelay.count) /
583
+ (stats.deactivateCount + stats.tallyCount)
584
+ ).toFixed(2)
585
+ )
586
+ : 0,
587
+ }))
588
+ .sort((a, b) => b.date.localeCompare(a.date)),
589
+ },
590
+ };
591
+ } catch (error) {
592
+ return {
593
+ code: 404,
594
+ error: {
595
+ message: 'Query miss rate failed',
596
+ type: ERROR.ERROR_QUERY_MISS_RATE_FAILED,
597
+ },
598
+ };
599
+ }
600
+ }
263
601
  }