@human-protocol/sdk 4.2.0 → 5.0.0-beta.0

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.
@@ -1,5 +1,5 @@
1
1
  import gql from 'graphql-tag';
2
- import { IEscrowsFilter } from '../../interfaces';
2
+ import { ICancellationRefundFilter, IEscrowsFilter } from '../../interfaces';
3
3
 
4
4
  const ESCROW_FRAGMENT = gql`
5
5
  fragment EscrowFields on Escrow {
@@ -25,6 +25,18 @@ const ESCROW_FRAGMENT = gql`
25
25
  }
26
26
  `;
27
27
 
28
+ const CANCELLATION_REFUND_FRAGMENT = gql`
29
+ fragment CancellationRefundFields on CancellationRefundEvent {
30
+ id
31
+ escrowAddress
32
+ receiver
33
+ amount
34
+ block
35
+ timestamp
36
+ txHash
37
+ }
38
+ `;
39
+
28
40
  export const GET_ESCROW_BY_ADDRESS_QUERY = () => gql`
29
41
  query getEscrowByAddress($escrowAddress: String!) {
30
42
  escrow(id: $escrowAddress) {
@@ -124,3 +136,42 @@ export const GET_STATUS_UPDATES_QUERY = (
124
136
  }
125
137
  `;
126
138
  };
139
+
140
+ export const GET_CANCELLATION_REFUNDS_QUERY = (
141
+ filter: ICancellationRefundFilter
142
+ ) => gql`
143
+ query CancellationRefundEvents(
144
+ $escrowAddress: Bytes
145
+ $receiver: Bytes
146
+ $from: Int
147
+ $to: Int
148
+ $first: Int
149
+ $skip: Int
150
+ $orderDirection: OrderDirection
151
+ ) {
152
+ cancellationRefundEvents(
153
+ where: {
154
+ ${filter.escrowAddress ? 'escrowAddress: $escrowAddress' : ''}
155
+ ${filter.receiver ? 'receiver: $receiver' : ''}
156
+ ${filter.from ? 'timestamp_gte: $from' : ''}
157
+ ${filter.to ? 'timestamp_lte: $to' : ''}
158
+ }
159
+ first: $first
160
+ skip: $skip
161
+ orderBy: timestamp
162
+ orderDirection: $orderDirection
163
+ ) {
164
+ ...CancellationRefundFields
165
+ }
166
+ }
167
+ ${CANCELLATION_REFUND_FRAGMENT}
168
+ `;
169
+
170
+ export const GET_CANCELLATION_REFUND_BY_ADDRESS_QUERY = () => gql`
171
+ query getCancellationRefundByAddress($escrowAddress: String!) {
172
+ cancellationRefundEvents(where: { escrowAddress: $escrowAddress }) {
173
+ ...CancellationRefundFields
174
+ }
175
+ }
176
+ ${CANCELLATION_REFUND_FRAGMENT}
177
+ `;
@@ -5,12 +5,6 @@ const LEADER_FRAGMENT = gql`
5
5
  fragment OperatorFields on Operator {
6
6
  id
7
7
  address
8
- amountStaked
9
- amountLocked
10
- lockedUntilTimestamp
11
- amountWithdrawn
12
- amountSlashed
13
- reward
14
8
  amountJobsProcessed
15
9
  role
16
10
  fee
@@ -24,22 +18,30 @@ const LEADER_FRAGMENT = gql`
24
18
  reputationNetworks
25
19
  name
26
20
  category
21
+ staker {
22
+ stakedAmount
23
+ lockedAmount
24
+ withdrawnAmount
25
+ slashedAmount
26
+ lockedUntilTimestamp
27
+ lastDepositTimestamp
28
+ }
27
29
  }
28
30
  `;
29
31
 
30
32
  export const GET_LEADERS_QUERY = (filter: IOperatorsFilter) => {
31
- const { roles, minAmountStaked } = filter;
33
+ const { roles, minStakedAmount } = filter;
32
34
 
33
35
  const WHERE_CLAUSE = `
34
36
  where: {
35
- ${minAmountStaked ? `amountStaked_gte: $minAmountStaked` : ''}
37
+ ${minStakedAmount ? `staker_: { stakedAmount_gte: $minStakedAmount }` : ''}
36
38
  ${roles ? `role_in: $roles` : ''}
37
39
  }
38
40
  `;
39
41
 
40
42
  return gql`
41
43
  query getOperators(
42
- $minAmountStaked: Int,
44
+ $minStakedAmount: Int,
43
45
  $roles: [String!]
44
46
  $first: Int
45
47
  $skip: Int
@@ -0,0 +1,80 @@
1
+ import gql from 'graphql-tag';
2
+ import { IStakersFilter } from '../../interfaces';
3
+
4
+ const STAKER_FRAGMENT = gql`
5
+ fragment StakerFields on Staker {
6
+ id
7
+ address
8
+ stakedAmount
9
+ lockedAmount
10
+ withdrawnAmount
11
+ slashedAmount
12
+ lockedUntilTimestamp
13
+ lastDepositTimestamp
14
+ }
15
+ `;
16
+
17
+ export const GET_STAKERS_QUERY = (filter: IStakersFilter) => {
18
+ const {
19
+ minStakedAmount,
20
+ maxStakedAmount,
21
+ minLockedAmount,
22
+ maxLockedAmount,
23
+ minWithdrawnAmount,
24
+ maxWithdrawnAmount,
25
+ minSlashedAmount,
26
+ maxSlashedAmount,
27
+ } = filter;
28
+
29
+ const whereFields = [
30
+ minStakedAmount ? `stakedAmount_gte: $minStakedAmount` : '',
31
+ maxStakedAmount ? `stakedAmount_lte: $maxStakedAmount` : '',
32
+ minLockedAmount ? `lockedAmount_gte: $minLockedAmount` : '',
33
+ maxLockedAmount ? `lockedAmount_lte: $maxLockedAmount` : '',
34
+ minWithdrawnAmount ? `withdrawnAmount_gte: $minWithdrawnAmount` : '',
35
+ maxWithdrawnAmount ? `withdrawnAmount_lte: $maxWithdrawnAmount` : '',
36
+ minSlashedAmount ? `slashedAmount_gte: $minSlashedAmount` : '',
37
+ maxSlashedAmount ? `slashedAmount_lte: $maxSlashedAmount` : '',
38
+ ].filter(Boolean);
39
+
40
+ const WHERE_CLAUSE = whereFields.length
41
+ ? `where: { ${whereFields.join(', ')} }`
42
+ : '';
43
+
44
+ return gql`
45
+ query getStakers(
46
+ $minStakedAmount: BigInt
47
+ $maxStakedAmount: BigInt
48
+ $minLockedAmount: BigInt
49
+ $maxLockedAmount: BigInt
50
+ $minWithdrawnAmount: BigInt
51
+ $maxWithdrawnAmount: BigInt
52
+ $minSlashedAmount: BigInt
53
+ $maxSlashedAmount: BigInt
54
+ $orderBy: String
55
+ $orderDirection: OrderDirection
56
+ $first: Int
57
+ $skip: Int
58
+ ) {
59
+ stakers(
60
+ ${WHERE_CLAUSE}
61
+ orderBy: $orderBy
62
+ orderDirection: $orderDirection
63
+ first: $first
64
+ skip: $skip
65
+ ) {
66
+ ...StakerFields
67
+ }
68
+ }
69
+ ${STAKER_FRAGMENT}
70
+ `;
71
+ };
72
+
73
+ export const GET_STAKER_BY_ADDRESS_QUERY = gql`
74
+ query getStaker($id: String!) {
75
+ staker(id: $id) {
76
+ ...StakerFields
77
+ }
78
+ }
79
+ ${STAKER_FRAGMENT}
80
+ `;
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { StakingClient } from './staking';
1
+ import { StakingClient, StakingUtils } from './staking';
2
2
  import { StorageClient } from './storage';
3
3
  import { KVStoreClient, KVStoreUtils } from './kvstore';
4
4
  import { EscrowClient, EscrowUtils } from './escrow';
@@ -26,4 +26,5 @@ export {
26
26
  OperatorUtils,
27
27
  TransactionUtils,
28
28
  WorkerUtils,
29
+ StakingUtils,
29
30
  };
package/src/interfaces.ts CHANGED
@@ -10,12 +10,11 @@ export interface IOperator {
10
10
  id: string;
11
11
  chainId: ChainId;
12
12
  address: string;
13
- amountStaked: bigint;
14
- amountLocked: bigint;
13
+ stakedAmount: bigint;
14
+ lockedAmount: bigint;
15
15
  lockedUntilTimestamp: bigint;
16
- amountWithdrawn: bigint;
17
- amountSlashed: bigint;
18
- reward: bigint;
16
+ withdrawnAmount: bigint;
17
+ slashedAmount: bigint;
19
18
  amountJobsProcessed: bigint;
20
19
  role?: string;
21
20
  fee?: bigint;
@@ -31,16 +30,36 @@ export interface IOperator {
31
30
  category?: string;
32
31
  }
33
32
 
34
- export interface IOperatorSubgraph
35
- extends Omit<IOperator, 'jobTypes' | 'reputationNetworks' | 'chainId'> {
36
- jobTypes?: string;
33
+ export interface IOperatorSubgraph {
34
+ id: string;
35
+ address: string;
36
+ amountJobsProcessed: bigint;
37
+ role?: string;
38
+ fee?: bigint;
39
+ publicKey?: string;
40
+ webhookUrl?: string;
41
+ website?: string;
42
+ url?: string;
43
+ registrationNeeded?: boolean;
44
+ registrationInstructions?: string;
45
+ name?: string;
46
+ category?: string;
47
+ jobTypes?: string | string[];
37
48
  reputationNetworks?: { address: string }[];
49
+ staker?: {
50
+ stakedAmount: bigint;
51
+ lockedAmount: bigint;
52
+ lockedUntilTimestamp: bigint;
53
+ withdrawnAmount: bigint;
54
+ slashedAmount: bigint;
55
+ lastDepositTimestamp: bigint;
56
+ };
38
57
  }
39
58
 
40
59
  export interface IOperatorsFilter extends IPagination {
41
60
  chainId: ChainId;
42
61
  roles?: string[];
43
- minAmountStaked?: number;
62
+ minStakedAmount?: number;
44
63
  orderBy?: string;
45
64
  }
46
65
 
@@ -55,15 +74,6 @@ export interface IReputationNetworkSubgraph
55
74
  operators: IOperatorSubgraph[];
56
75
  }
57
76
 
58
- export interface IOperator {
59
- address: string;
60
- role?: string;
61
- url?: string;
62
- jobTypes?: string[];
63
- registrationNeeded?: boolean;
64
- registrationInstructions?: string;
65
- }
66
-
67
77
  export interface IEscrow {
68
78
  id: string;
69
79
  address: string;
@@ -208,3 +218,38 @@ export interface IWorkersFilter extends IPagination {
208
218
  address?: string;
209
219
  orderBy?: string;
210
220
  }
221
+
222
+ export interface IStaker {
223
+ address: string;
224
+ stakedAmount: bigint;
225
+ lockedAmount: bigint;
226
+ lockedUntil: bigint;
227
+ withdrawableAmount: bigint;
228
+ slashedAmount: bigint;
229
+ lastDepositTimestamp: bigint;
230
+ }
231
+
232
+ export interface IStakersFilter extends IPagination {
233
+ chainId: ChainId;
234
+ minStakedAmount?: string;
235
+ maxStakedAmount?: string;
236
+ minLockedAmount?: string;
237
+ maxLockedAmount?: string;
238
+ minWithdrawnAmount?: string;
239
+ maxWithdrawnAmount?: string;
240
+ minSlashedAmount?: string;
241
+ maxSlashedAmount?: string;
242
+ orderBy?:
243
+ | 'stakedAmount'
244
+ | 'lockedAmount'
245
+ | 'withdrawnAmount'
246
+ | 'slashedAmount'
247
+ | 'lastDepositTimestamp';
248
+ }
249
+ export interface ICancellationRefundFilter extends IPagination {
250
+ chainId: ChainId;
251
+ escrowAddress?: string;
252
+ receiver?: string;
253
+ from?: Date;
254
+ to?: Date;
255
+ }
package/src/operator.ts CHANGED
@@ -60,33 +60,10 @@ export class OperatorUtils {
60
60
  });
61
61
 
62
62
  if (!operator) {
63
- return (operator as IOperator) || null;
63
+ return null as any;
64
64
  }
65
65
 
66
- let jobTypes: string[] = [];
67
- let reputationNetworks: string[] = [];
68
-
69
- if (typeof operator.jobTypes === 'string') {
70
- jobTypes = operator.jobTypes.split(',');
71
- } else if (Array.isArray(operator.jobTypes)) {
72
- jobTypes = operator.jobTypes;
73
- }
74
-
75
- if (
76
- operator.reputationNetworks &&
77
- Array.isArray(operator.reputationNetworks)
78
- ) {
79
- reputationNetworks = operator.reputationNetworks.map(
80
- (network) => network.address
81
- );
82
- }
83
-
84
- return {
85
- ...operator,
86
- jobTypes,
87
- reputationNetworks,
88
- chainId,
89
- };
66
+ return mapOperator(operator, chainId);
90
67
  }
91
68
 
92
69
  /**
@@ -109,8 +86,6 @@ export class OperatorUtils {
109
86
  public static async getOperators(
110
87
  filter: IOperatorsFilter
111
88
  ): Promise<IOperator[]> {
112
- let operators_data: IOperator[] = [];
113
-
114
89
  const first =
115
90
  filter.first !== undefined && filter.first > 0
116
91
  ? Math.min(filter.first, 1000)
@@ -119,6 +94,15 @@ export class OperatorUtils {
119
94
  filter.skip !== undefined && filter.skip >= 0 ? filter.skip : 0;
120
95
  const orderDirection = filter.orderDirection || OrderDirection.DESC;
121
96
 
97
+ let orderBy = filter.orderBy;
98
+ if (filter.orderBy === 'stakedAmount') orderBy = 'staker__stakedAmount';
99
+ else if (filter.orderBy === 'lockedAmount')
100
+ orderBy = 'staker__lockedAmount';
101
+ else if (filter.orderBy === 'withdrawnAmount')
102
+ orderBy = 'staker__withdrawnAmount';
103
+ else if (filter.orderBy === 'slashedAmount')
104
+ orderBy = 'staker__slashedAmount';
105
+
122
106
  const networkData = NETWORKS[filter.chainId];
123
107
 
124
108
  if (!networkData) {
@@ -128,9 +112,9 @@ export class OperatorUtils {
128
112
  const { operators } = await gqlFetch<{
129
113
  operators: IOperatorSubgraph[];
130
114
  }>(getSubgraphUrl(networkData), GET_LEADERS_QUERY(filter), {
131
- minAmountStaked: filter?.minAmountStaked,
115
+ minStakedAmount: filter?.minStakedAmount,
132
116
  roles: filter?.roles,
133
- orderBy: filter?.orderBy,
117
+ orderBy: orderBy,
134
118
  orderDirection: orderDirection,
135
119
  first: first,
136
120
  skip: skip,
@@ -140,35 +124,7 @@ export class OperatorUtils {
140
124
  return [];
141
125
  }
142
126
 
143
- operators_data = operators_data.concat(
144
- operators.map((operator) => {
145
- let jobTypes: string[] = [];
146
- let reputationNetworks: string[] = [];
147
-
148
- if (typeof operator.jobTypes === 'string') {
149
- jobTypes = operator.jobTypes.split(',');
150
- } else if (Array.isArray(operator.jobTypes)) {
151
- jobTypes = operator.jobTypes;
152
- }
153
-
154
- if (
155
- operator.reputationNetworks &&
156
- Array.isArray(operator.reputationNetworks)
157
- ) {
158
- reputationNetworks = operator.reputationNetworks.map(
159
- (network) => network.address
160
- );
161
- }
162
-
163
- return {
164
- ...operator,
165
- jobTypes,
166
- reputationNetworks,
167
- chainId: filter.chainId,
168
- };
169
- })
170
- );
171
- return operators_data;
127
+ return operators.map((operator) => mapOperator(operator, filter.chainId));
172
128
  }
173
129
 
174
130
  /**
@@ -206,24 +162,9 @@ export class OperatorUtils {
206
162
 
207
163
  if (!reputationNetwork) return [];
208
164
 
209
- return reputationNetwork.operators.map((operator) => {
210
- let jobTypes: string[] = [];
211
-
212
- if (typeof operator.jobTypes === 'string') {
213
- jobTypes = operator.jobTypes.split(',');
214
- } else if (Array.isArray(operator.jobTypes)) {
215
- jobTypes = operator.jobTypes;
216
- }
217
-
218
- return {
219
- chainId,
220
- ...operator,
221
- jobTypes,
222
- reputationNetworks: operator.reputationNetworks?.map(
223
- (network) => network.address
224
- ),
225
- };
226
- });
165
+ return reputationNetwork.operators.map((operator) =>
166
+ mapOperator(operator, chainId)
167
+ );
227
168
  }
228
169
 
229
170
  /**
@@ -270,3 +211,48 @@ export class OperatorUtils {
270
211
  });
271
212
  }
272
213
  }
214
+
215
+ function mapOperator(operator: IOperatorSubgraph, chainId: ChainId): IOperator {
216
+ const staker = operator?.staker;
217
+ let jobTypes: string[] = [];
218
+ let reputationNetworks: string[] = [];
219
+
220
+ if (typeof operator.jobTypes === 'string') {
221
+ jobTypes = operator.jobTypes.split(',');
222
+ } else if (Array.isArray(operator.jobTypes)) {
223
+ jobTypes = operator.jobTypes;
224
+ }
225
+
226
+ if (
227
+ operator.reputationNetworks &&
228
+ Array.isArray(operator.reputationNetworks)
229
+ ) {
230
+ reputationNetworks = operator.reputationNetworks.map(
231
+ (network) => network.address
232
+ );
233
+ }
234
+
235
+ return {
236
+ id: operator.id,
237
+ chainId,
238
+ address: operator.address,
239
+ stakedAmount: BigInt(staker?.stakedAmount || 0),
240
+ lockedAmount: BigInt(staker?.lockedAmount || 0),
241
+ lockedUntilTimestamp: BigInt(staker?.lockedUntilTimestamp || 0),
242
+ withdrawnAmount: BigInt(staker?.withdrawnAmount || 0),
243
+ slashedAmount: BigInt(staker?.slashedAmount || 0),
244
+ amountJobsProcessed: BigInt(operator.amountJobsProcessed || 0),
245
+ role: operator.role,
246
+ fee: operator.fee ? BigInt(operator.fee) : undefined,
247
+ publicKey: operator.publicKey,
248
+ webhookUrl: operator.webhookUrl,
249
+ website: operator.website,
250
+ url: operator.url,
251
+ jobTypes,
252
+ registrationNeeded: operator.registrationNeeded,
253
+ registrationInstructions: operator.registrationInstructions,
254
+ reputationNetworks,
255
+ name: operator.name,
256
+ category: operator.category,
257
+ };
258
+ }
package/src/staking.ts CHANGED
@@ -7,10 +7,11 @@ import {
7
7
  Staking__factory,
8
8
  } from '@human-protocol/core/typechain-types';
9
9
  import { ContractRunner, Overrides, ethers } from 'ethers';
10
+ import gqlFetch from 'graphql-request';
10
11
  import { BaseEthersClient } from './base';
11
12
  import { NETWORKS } from './constants';
12
13
  import { requiresSigner } from './decorators';
13
- import { ChainId } from './enums';
14
+ import { ChainId, OrderDirection } from './enums';
14
15
  import {
15
16
  ErrorEscrowAddressIsNotProvidedByFactory,
16
17
  ErrorInvalidEscrowAddressProvided,
@@ -19,11 +20,16 @@ import {
19
20
  ErrorInvalidStakingValueSign,
20
21
  ErrorInvalidStakingValueType,
21
22
  ErrorProviderDoesNotExist,
23
+ ErrorStakerNotFound,
22
24
  ErrorUnsupportedChainID,
23
25
  } from './error';
26
+ import { IStaker, IStakersFilter, StakerInfo } from './interfaces';
24
27
  import { NetworkData } from './types';
25
- import { throwError } from './utils';
26
- import { StakerInfo } from './interfaces';
28
+ import { getSubgraphUrl, throwError } from './utils';
29
+ import {
30
+ GET_STAKER_BY_ADDRESS_QUERY,
31
+ GET_STAKERS_QUERY,
32
+ } from './graphql/queries/staking';
27
33
 
28
34
  /**
29
35
  * ## Introduction
@@ -439,6 +445,7 @@ export class StakingClient extends BaseEthersClient {
439
445
 
440
446
  try {
441
447
  const stakerInfo = await this.stakingContract.stakes(stakerAddress);
448
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
442
449
  const currentBlock = await this.runner.provider!.getBlockNumber();
443
450
 
444
451
  const tokensWithdrawable =
@@ -465,3 +472,99 @@ export class StakingClient extends BaseEthersClient {
465
472
  }
466
473
  }
467
474
  }
475
+
476
+ /**
477
+ * Utility class for Staking-related subgraph queries.
478
+ */
479
+ export class StakingUtils {
480
+ /**
481
+ * Gets staking info for a staker from the subgraph.
482
+ *
483
+ * @param {ChainId} chainId Network in which the staking contract is deployed
484
+ * @param {string} stakerAddress Address of the staker
485
+ * @returns {Promise<IStaker>} Staker info from subgraph
486
+ */
487
+ public static async getStaker(
488
+ chainId: ChainId,
489
+ stakerAddress: string
490
+ ): Promise<IStaker> {
491
+ if (!ethers.isAddress(stakerAddress)) {
492
+ throw ErrorInvalidStakerAddressProvided;
493
+ }
494
+
495
+ const networkData: NetworkData | undefined = NETWORKS[chainId];
496
+ if (!networkData) {
497
+ throw ErrorUnsupportedChainID;
498
+ }
499
+
500
+ const { staker } = await gqlFetch<{ staker: IStaker }>(
501
+ getSubgraphUrl(networkData),
502
+ GET_STAKER_BY_ADDRESS_QUERY,
503
+ { id: stakerAddress.toLowerCase() }
504
+ );
505
+
506
+ if (!staker) {
507
+ throw ErrorStakerNotFound;
508
+ }
509
+
510
+ return staker;
511
+ }
512
+
513
+ /**
514
+ * Gets all stakers from the subgraph with filters, pagination and ordering.
515
+ *
516
+ * @returns {Promise<IStaker[]>} Array of stakers
517
+ */
518
+ public static async getStakers(filter: IStakersFilter): Promise<IStaker[]> {
519
+ const first =
520
+ filter.first !== undefined ? Math.min(filter.first, 1000) : 10;
521
+ const skip = filter.skip || 0;
522
+ const orderDirection = filter.orderDirection || OrderDirection.DESC;
523
+ const orderBy = filter.orderBy || 'lastDepositTimestamp';
524
+
525
+ const networkData = NETWORKS[filter.chainId];
526
+ if (!networkData) {
527
+ throw ErrorUnsupportedChainID;
528
+ }
529
+
530
+ const { stakers } = await gqlFetch<{ stakers: IStaker[] }>(
531
+ getSubgraphUrl(networkData),
532
+ GET_STAKERS_QUERY(filter),
533
+ {
534
+ minStakedAmount: filter.minStakedAmount
535
+ ? filter.minStakedAmount
536
+ : undefined,
537
+ maxStakedAmount: filter.maxStakedAmount
538
+ ? filter.maxStakedAmount
539
+ : undefined,
540
+ minLockedAmount: filter.minLockedAmount
541
+ ? filter.minLockedAmount
542
+ : undefined,
543
+ maxLockedAmount: filter.maxLockedAmount
544
+ ? filter.maxLockedAmount
545
+ : undefined,
546
+ minWithdrawnAmount: filter.minWithdrawnAmount
547
+ ? filter.minWithdrawnAmount
548
+ : undefined,
549
+ maxWithdrawnAmount: filter.maxWithdrawnAmount
550
+ ? filter.maxWithdrawnAmount
551
+ : undefined,
552
+ minSlashedAmount: filter.minSlashedAmount
553
+ ? filter.minSlashedAmount
554
+ : undefined,
555
+ maxSlashedAmount: filter.maxSlashedAmount
556
+ ? filter.maxSlashedAmount
557
+ : undefined,
558
+ orderBy: orderBy,
559
+ orderDirection: orderDirection,
560
+ first: first,
561
+ skip: skip,
562
+ }
563
+ );
564
+ if (!stakers) {
565
+ return [];
566
+ }
567
+
568
+ return stakers;
569
+ }
570
+ }