diff --git a/.changeset/shaggy-months-post.md b/.changeset/shaggy-months-post.md new file mode 100644 index 0000000000..183d64c44e --- /dev/null +++ b/.changeset/shaggy-months-post.md @@ -0,0 +1,5 @@ +--- +"@human-protocol/sdk": patch +--- + +Split combined domain files into module folders with explicit files per responsibility. diff --git a/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts b/packages/sdk/typescript/human-protocol-sdk/src/encryption/encryption.ts similarity index 50% rename from packages/sdk/typescript/human-protocol-sdk/src/encryption.ts rename to packages/sdk/typescript/human-protocol-sdk/src/encryption/encryption.ts index 208f3b9513..0329c1dd25 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/encryption.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/encryption/encryption.ts @@ -1,21 +1,5 @@ import * as openpgp from 'openpgp'; -import { IKeyPair } from './interfaces'; - -/** - * Type representing the data type of a message. - * It can be either a string or a Uint8Array. - * - * @public - */ -export type MessageDataType = string | Uint8Array; - -function makeMessageDataBinary(message: MessageDataType): Uint8Array { - if (typeof message === 'string') { - return Buffer.from(message); - } - - return message; -} +import { makeMessageDataBinary, MessageDataType } from './types'; /** * Class for signing and decrypting messages. @@ -191,179 +175,3 @@ export class Encryption { return cleartextMessage; } } - -/** - * Utility class for encryption-related operations. - */ -export class EncryptionUtils { - /** - * This function verifies the signature of a signed message using the public key. - * - * @param message - Message to verify. - * @param publicKey - Public key to verify that the message was signed by a specific source. - * @returns True if verified. False if not verified. - * - * @example - * ```ts - * import { EncryptionUtils } from '@human-protocol/sdk'; - * - * const publicKey = '-----BEGIN PGP PUBLIC KEY BLOCK-----...'; - * const result = await EncryptionUtils.verify('message', publicKey); - * console.log('Verification result:', result); - * ``` - */ - public static async verify( - message: string, - publicKey: string - ): Promise { - const pgpPublicKey = await openpgp.readKey({ armoredKey: publicKey }); - const signedMessage = await openpgp.readCleartextMessage({ - cleartextMessage: message, - }); - - const verificationResult = await signedMessage.verify([pgpPublicKey]); - const { verified } = verificationResult[0]; - - try { - return await verified; - } catch { - return false; - } - } - - /** - * This function gets signed data from a signed message. - * - * @param message - Message. - * @returns Signed data. - * @throws Error If data could not be extracted from the message - * - * @example - * ```ts - * import { EncryptionUtils } from '@human-protocol/sdk'; - * - * const signedData = await EncryptionUtils.getSignedData('message'); - * console.log('Signed data:', signedData); - * ``` - */ - public static async getSignedData(message: string): Promise { - const signedMessage = await openpgp.readCleartextMessage({ - cleartextMessage: message, - }); - - try { - return signedMessage.getText(); - } catch (e) { - throw new Error('Could not get data: ' + e.message); - } - } - - /** - * This function generates a key pair for encryption and decryption. - * - * @param name - Name for the key pair. - * @param email - Email for the key pair. - * @param passphrase - Passphrase to encrypt the private key (optional, defaults to empty string). - * @returns Key pair generated. - * - * @example - * ```ts - * import { EncryptionUtils } from '@human-protocol/sdk'; - * - * const name = 'YOUR_NAME'; - * const email = 'YOUR_EMAIL'; - * const passphrase = 'YOUR_PASSPHRASE'; - * const keyPair = await EncryptionUtils.generateKeyPair(name, email, passphrase); - * console.log('Public key:', keyPair.publicKey); - * ``` - */ - public static async generateKeyPair( - name: string, - email: string, - passphrase = '' - ): Promise { - const { privateKey, publicKey, revocationCertificate } = - await openpgp.generateKey({ - type: 'ecc', - curve: 'ed25519Legacy', - userIDs: [{ name: name, email: email }], - passphrase: passphrase, - format: 'armored', - }); - - return { - passphrase: passphrase, - privateKey, - publicKey, - revocationCertificate, - }; - } - - /** - * This function encrypts a message using the specified public keys. - * - * @param message - Message to encrypt. - * @param publicKeys - Array of public keys to use for encryption. - * @returns Message encrypted. - * - * @example - * ```ts - * import { EncryptionUtils } from '@human-protocol/sdk'; - * - * const publicKey1 = '-----BEGIN PGP PUBLIC KEY BLOCK-----...'; - * const publicKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----...'; - * const publicKeys = [publicKey1, publicKey2]; - * const encryptedMessage = await EncryptionUtils.encrypt('message', publicKeys); - * console.log('Encrypted message:', encryptedMessage); - * ``` - */ - public static async encrypt( - message: MessageDataType, - publicKeys: string[] - ): Promise { - const pgpPublicKeys = await Promise.all( - publicKeys.map((armoredKey) => openpgp.readKey({ armoredKey })) - ); - - const pgpMessage = await openpgp.createMessage({ - binary: makeMessageDataBinary(message), - }); - const encrypted = await openpgp.encrypt({ - message: pgpMessage, - encryptionKeys: pgpPublicKeys, - format: 'armored', - }); - - return encrypted as string; - } - - /** - * Verifies if a message appears to be encrypted with OpenPGP. - * - * @param message - Message to verify. - * @returns `true` if the message appears to be encrypted, `false` if not. - * - * @example - * ```ts - * import { EncryptionUtils } from '@human-protocol/sdk'; - * - * const message = '-----BEGIN PGP MESSAGE-----...'; - * const isEncrypted = EncryptionUtils.isEncrypted(message); - * - * if (isEncrypted) { - * console.log('The message is encrypted with OpenPGP.'); - * } else { - * console.log('The message is not encrypted with OpenPGP.'); - * } - * ``` - */ - public static isEncrypted(message: string): boolean { - const startMarker = '-----BEGIN PGP MESSAGE-----'; - const endMarker = '-----END PGP MESSAGE-----'; - - const hasStartMarker = message.includes(startMarker); - const hasEndMarker = message.includes(endMarker); - - return hasStartMarker && hasEndMarker; - } -} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/encryption/encryption_utils.ts b/packages/sdk/typescript/human-protocol-sdk/src/encryption/encryption_utils.ts new file mode 100644 index 0000000000..0d75eaf028 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/encryption/encryption_utils.ts @@ -0,0 +1,179 @@ +import * as openpgp from 'openpgp'; +import { IKeyPair } from '../interfaces'; +import { makeMessageDataBinary, MessageDataType } from './types'; + +/** + * Utility class for encryption-related operations. + */ +export class EncryptionUtils { + /** + * This function verifies the signature of a signed message using the public key. + * + * @param message - Message to verify. + * @param publicKey - Public key to verify that the message was signed by a specific source. + * @returns True if verified. False if not verified. + * + * @example + * ```ts + * import { EncryptionUtils } from '@human-protocol/sdk'; + * + * const publicKey = '-----BEGIN PGP PUBLIC KEY BLOCK-----...'; + * const result = await EncryptionUtils.verify('message', publicKey); + * console.log('Verification result:', result); + * ``` + */ + public static async verify( + message: string, + publicKey: string + ): Promise { + const pgpPublicKey = await openpgp.readKey({ armoredKey: publicKey }); + const signedMessage = await openpgp.readCleartextMessage({ + cleartextMessage: message, + }); + + const verificationResult = await signedMessage.verify([pgpPublicKey]); + const { verified } = verificationResult[0]; + + try { + return await verified; + } catch { + return false; + } + } + + /** + * This function gets signed data from a signed message. + * + * @param message - Message. + * @returns Signed data. + * @throws Error If data could not be extracted from the message + * + * @example + * ```ts + * import { EncryptionUtils } from '@human-protocol/sdk'; + * + * const signedData = await EncryptionUtils.getSignedData('message'); + * console.log('Signed data:', signedData); + * ``` + */ + public static async getSignedData(message: string): Promise { + const signedMessage = await openpgp.readCleartextMessage({ + cleartextMessage: message, + }); + + try { + return signedMessage.getText(); + } catch (e) { + throw new Error('Could not get data: ' + e.message); + } + } + + /** + * This function generates a key pair for encryption and decryption. + * + * @param name - Name for the key pair. + * @param email - Email for the key pair. + * @param passphrase - Passphrase to encrypt the private key (optional, defaults to empty string). + * @returns Key pair generated. + * + * @example + * ```ts + * import { EncryptionUtils } from '@human-protocol/sdk'; + * + * const name = 'YOUR_NAME'; + * const email = 'YOUR_EMAIL'; + * const passphrase = 'YOUR_PASSPHRASE'; + * const keyPair = await EncryptionUtils.generateKeyPair(name, email, passphrase); + * console.log('Public key:', keyPair.publicKey); + * ``` + */ + public static async generateKeyPair( + name: string, + email: string, + passphrase = '' + ): Promise { + const { privateKey, publicKey, revocationCertificate } = + await openpgp.generateKey({ + type: 'ecc', + curve: 'ed25519Legacy', + userIDs: [{ name: name, email: email }], + passphrase: passphrase, + format: 'armored', + }); + + return { + passphrase: passphrase, + privateKey, + publicKey, + revocationCertificate, + }; + } + + /** + * This function encrypts a message using the specified public keys. + * + * @param message - Message to encrypt. + * @param publicKeys - Array of public keys to use for encryption. + * @returns Message encrypted. + * + * @example + * ```ts + * import { EncryptionUtils } from '@human-protocol/sdk'; + * + * const publicKey1 = '-----BEGIN PGP PUBLIC KEY BLOCK-----...'; + * const publicKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----...'; + * const publicKeys = [publicKey1, publicKey2]; + * const encryptedMessage = await EncryptionUtils.encrypt('message', publicKeys); + * console.log('Encrypted message:', encryptedMessage); + * ``` + */ + public static async encrypt( + message: MessageDataType, + publicKeys: string[] + ): Promise { + const pgpPublicKeys = await Promise.all( + publicKeys.map((armoredKey) => openpgp.readKey({ armoredKey })) + ); + + const pgpMessage = await openpgp.createMessage({ + binary: makeMessageDataBinary(message), + }); + const encrypted = await openpgp.encrypt({ + message: pgpMessage, + encryptionKeys: pgpPublicKeys, + format: 'armored', + }); + + return encrypted as string; + } + + /** + * Verifies if a message appears to be encrypted with OpenPGP. + * + * @param message - Message to verify. + * @returns `true` if the message appears to be encrypted, `false` if not. + * + * @example + * ```ts + * import { EncryptionUtils } from '@human-protocol/sdk'; + * + * const message = '-----BEGIN PGP MESSAGE-----...'; + * const isEncrypted = EncryptionUtils.isEncrypted(message); + * + * if (isEncrypted) { + * console.log('The message is encrypted with OpenPGP.'); + * } else { + * console.log('The message is not encrypted with OpenPGP.'); + * } + * ``` + */ + public static isEncrypted(message: string): boolean { + const startMarker = '-----BEGIN PGP MESSAGE-----'; + const endMarker = '-----END PGP MESSAGE-----'; + + const hasStartMarker = message.includes(startMarker); + const hasEndMarker = message.includes(endMarker); + + return hasStartMarker && hasEndMarker; + } +} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/encryption/index.ts b/packages/sdk/typescript/human-protocol-sdk/src/encryption/index.ts new file mode 100644 index 0000000000..78d80c0783 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/encryption/index.ts @@ -0,0 +1,3 @@ +export { Encryption } from './encryption'; +export { EncryptionUtils } from './encryption_utils'; +export type { MessageDataType } from './types'; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/encryption/types.ts b/packages/sdk/typescript/human-protocol-sdk/src/encryption/types.ts new file mode 100644 index 0000000000..7d7ee473c6 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/encryption/types.ts @@ -0,0 +1,15 @@ +/** + * Type representing the data type of a message. + * It can be either a string or a Uint8Array. + * + * @public + */ +export type MessageDataType = string | Uint8Array; + +export function makeMessageDataBinary(message: MessageDataType): Uint8Array { + if (typeof message === 'string') { + return Buffer.from(message); + } + + return message; +} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts b/packages/sdk/typescript/human-protocol-sdk/src/escrow/escrow_client.ts similarity index 77% rename from packages/sdk/typescript/human-protocol-sdk/src/escrow.ts rename to packages/sdk/typescript/human-protocol-sdk/src/escrow/escrow_client.ts index 8b535a74ac..63e0eb72fc 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/escrow/escrow_client.ts @@ -10,10 +10,10 @@ import { HMToken__factory, } from '@human-protocol/core/typechain-types'; import { ContractRunner, EventLog, Overrides, Signer, ethers } from 'ethers'; -import { BaseEthersClient } from './base'; -import { ESCROW_BULK_PAYOUT_MAX_ITEMS, NETWORKS } from './constants'; -import { requiresSigner } from './decorators'; -import { ChainId, OrderDirection } from './enums'; +import { BaseEthersClient } from '../base'; +import { ESCROW_BULK_PAYOUT_MAX_ITEMS, NETWORKS } from '../constants'; +import { requiresSigner } from '../decorators'; +import { ChainId } from '../enums'; import { ErrorAmountMustBeGreaterThanZero, ErrorAmountsCannotBeEmptyArray, @@ -21,7 +21,6 @@ import { ErrorEscrowAddressIsNotProvidedByFactory, ErrorEscrowDoesNotHaveEnoughBalance, ErrorHashIsEmptyString, - ErrorInvalidAddress, ErrorInvalidEscrowAddressProvided, ErrorInvalidExchangeOracleAddressProvided, ErrorInvalidManifest, @@ -40,46 +39,15 @@ import { ErrorUnsupportedChainID, InvalidEthereumAddressError, WarnVersionMismatch, -} from './error'; -import { - CancellationRefundData, - EscrowData, - GET_CANCELLATION_REFUNDS_QUERY, - GET_CANCELLATION_REFUND_BY_ADDRESS_QUERY, - GET_ESCROWS_QUERY, - GET_ESCROW_BY_ADDRESS_QUERY, - GET_PAYOUTS_QUERY, - GET_STATUS_UPDATES_QUERY, - PayoutData, - StatusEvent, -} from './graphql'; -import { - IEscrow, - IEscrowConfig, - IEscrowsFilter, - IPayoutFilter, - IStatusEventFilter, - IStatusEvent, - ICancellationRefund, - ICancellationRefundFilter, - IPayout, - IEscrowWithdraw, - SubgraphOptions, -} from './interfaces'; +} from '../error'; +import { IEscrowConfig, IEscrowWithdraw } from '../interfaces'; import { EscrowStatus, NetworkData, TransactionLikeWithNonce, TransactionOverrides, -} from './types'; -import { - getSubgraphUrl, - getUnixTimestamp, - customGqlFetch, - isValidJson, - isValidUrl, - throwError, -} from './utils'; +} from '../types'; +import { isValidJson, isValidUrl, throwError } from '../utils'; /** * Client to perform actions on Escrow contracts and obtain information from the contracts. @@ -1682,480 +1650,3 @@ export class EscrowClient extends BaseEthersClient { } } } -/** - * Utility helpers for escrow-related queries. - * - * @example - * ```ts - * import { ChainId, EscrowUtils } from '@human-protocol/sdk'; - * - * const escrows = await EscrowUtils.getEscrows({ - * chainId: ChainId.POLYGON_AMOY - * }); - * console.log('Escrows:', escrows); - * ``` - */ -export class EscrowUtils { - /** - * This function returns an array of escrows based on the specified filter parameters. - * - * @param filter - Filter parameters. - * @param options - Optional configuration for subgraph requests. - * @returns List of escrows that match the filter. - * @throws ErrorInvalidAddress If any filter address is invalid - * @throws ErrorUnsupportedChainID If the chain ID is not supported - * - * @example - * ```ts - * import { ChainId, EscrowStatus } from '@human-protocol/sdk'; - * - * const filters = { - * status: EscrowStatus.Pending, - * from: new Date(2023, 4, 8), - * to: new Date(2023, 5, 8), - * chainId: ChainId.POLYGON_AMOY - * }; - * const escrows = await EscrowUtils.getEscrows(filters); - * console.log('Found escrows:', escrows.length); - * ``` - */ - public static async getEscrows( - filter: IEscrowsFilter, - options?: SubgraphOptions - ): Promise { - if (filter.launcher && !ethers.isAddress(filter.launcher)) { - throw ErrorInvalidAddress; - } - - if (filter.recordingOracle && !ethers.isAddress(filter.recordingOracle)) { - throw ErrorInvalidAddress; - } - - if (filter.reputationOracle && !ethers.isAddress(filter.reputationOracle)) { - throw ErrorInvalidAddress; - } - - if (filter.exchangeOracle && !ethers.isAddress(filter.exchangeOracle)) { - throw ErrorInvalidAddress; - } - - const first = - filter.first !== undefined ? Math.min(filter.first, 1000) : 10; - const skip = filter.skip || 0; - const orderDirection = filter.orderDirection || OrderDirection.DESC; - - const networkData = NETWORKS[filter.chainId]; - - if (!networkData) { - throw ErrorUnsupportedChainID; - } - - let statuses; - if (filter.status !== undefined) { - statuses = Array.isArray(filter.status) ? filter.status : [filter.status]; - statuses = statuses.map((status) => EscrowStatus[status]); - } - const { escrows } = await customGqlFetch<{ escrows: EscrowData[] }>( - getSubgraphUrl(networkData), - GET_ESCROWS_QUERY(filter), - { - ...filter, - launcher: filter.launcher?.toLowerCase(), - reputationOracle: filter.reputationOracle?.toLowerCase(), - recordingOracle: filter.recordingOracle?.toLowerCase(), - exchangeOracle: filter.exchangeOracle?.toLowerCase(), - status: statuses, - from: filter.from ? getUnixTimestamp(filter.from) : undefined, - to: filter.to ? getUnixTimestamp(filter.to) : undefined, - orderDirection: orderDirection, - first: first, - skip: skip, - }, - options - ); - return (escrows || []).map((e) => mapEscrow(e, networkData.chainId)); - } - - /** - * This function returns the escrow data for a given address. - * - * > This uses Subgraph - * - * @param chainId - Network in which the escrow has been deployed - * @param escrowAddress - Address of the escrow - * @param options - Optional configuration for subgraph requests. - * @returns Escrow data or null if not found. - * @throws ErrorUnsupportedChainID If the chain ID is not supported - * @throws ErrorInvalidAddress If the escrow address is invalid - * - * @example - * ```ts - * import { ChainId } from '@human-protocol/sdk'; - * - * const escrow = await EscrowUtils.getEscrow( - * ChainId.POLYGON_AMOY, - * "0x1234567890123456789012345678901234567890" - * ); - * if (escrow) { - * console.log('Escrow status:', escrow.status); - * } - * ``` - */ - public static async getEscrow( - chainId: ChainId, - escrowAddress: string, - options?: SubgraphOptions - ): Promise { - const networkData = NETWORKS[chainId]; - - if (!networkData) { - throw ErrorUnsupportedChainID; - } - - if (escrowAddress && !ethers.isAddress(escrowAddress)) { - throw ErrorInvalidAddress; - } - - const { escrow } = await customGqlFetch<{ escrow: EscrowData | null }>( - getSubgraphUrl(networkData), - GET_ESCROW_BY_ADDRESS_QUERY(), - { escrowAddress: escrowAddress.toLowerCase() }, - options - ); - if (!escrow) return null; - - return mapEscrow(escrow, networkData.chainId); - } - - /** - * This function returns the status events for a given set of networks within an optional date range. - * - * > This uses Subgraph - * - * @param filter - Filter parameters. - * @param options - Optional configuration for subgraph requests. - * @returns Array of status events with their corresponding statuses. - * @throws ErrorInvalidAddress If the launcher address is invalid - * @throws ErrorUnsupportedChainID If the chain ID is not supported - * - * @example - * ```ts - * import { ChainId, EscrowStatus } from '@human-protocol/sdk'; - * - * const fromDate = new Date('2023-01-01'); - * const toDate = new Date('2023-12-31'); - * const statusEvents = await EscrowUtils.getStatusEvents({ - * chainId: ChainId.POLYGON, - * statuses: [EscrowStatus.Pending, EscrowStatus.Complete], - * from: fromDate, - * to: toDate - * }); - * console.log('Status events:', statusEvents.length); - * ``` - */ - public static async getStatusEvents( - filter: IStatusEventFilter, - options?: SubgraphOptions - ): Promise { - const { - chainId, - statuses, - from, - to, - launcher, - first = 10, - skip = 0, - orderDirection = OrderDirection.DESC, - } = filter; - - if (launcher && !ethers.isAddress(launcher)) { - throw ErrorInvalidAddress; - } - - const networkData = NETWORKS[chainId]; - if (!networkData) { - throw ErrorUnsupportedChainID; - } - - // If statuses are not provided, use all statuses except Launched - const effectiveStatuses = statuses ?? [ - EscrowStatus.Launched, - EscrowStatus.Pending, - EscrowStatus.Partial, - EscrowStatus.Paid, - EscrowStatus.Complete, - EscrowStatus.Cancelled, - ]; - - const statusNames = effectiveStatuses.map((status) => EscrowStatus[status]); - - const data = await customGqlFetch<{ - escrowStatusEvents: StatusEvent[]; - }>( - getSubgraphUrl(networkData), - GET_STATUS_UPDATES_QUERY(from, to, launcher), - { - status: statusNames, - from: from ? getUnixTimestamp(from) : undefined, - to: to ? getUnixTimestamp(to) : undefined, - launcher: launcher || undefined, - orderDirection, - first: Math.min(first, 1000), - skip, - }, - options - ); - - if (!data || !data['escrowStatusEvents']) { - return []; - } - - return data['escrowStatusEvents'].map((event) => ({ - timestamp: Number(event.timestamp) * 1000, - escrowAddress: event.escrowAddress, - status: EscrowStatus[event.status as keyof typeof EscrowStatus], - chainId, - })); - } - - /** - * This function returns the payouts for a given set of networks. - * - * > This uses Subgraph - * - * @param filter - Filter parameters. - * @param options - Optional configuration for subgraph requests. - * @returns List of payouts matching the filters. - * @throws ErrorUnsupportedChainID If the chain ID is not supported - * @throws ErrorInvalidAddress If any filter address is invalid - * - * @example - * ```ts - * import { ChainId } from '@human-protocol/sdk'; - * - * const payouts = await EscrowUtils.getPayouts({ - * chainId: ChainId.POLYGON, - * escrowAddress: '0x1234567890123456789012345678901234567890', - * recipient: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', - * from: new Date('2023-01-01'), - * to: new Date('2023-12-31') - * }); - * console.log('Payouts:', payouts.length); - * ``` - */ - public static async getPayouts( - filter: IPayoutFilter, - options?: SubgraphOptions - ): Promise { - const networkData = NETWORKS[filter.chainId]; - if (!networkData) { - throw ErrorUnsupportedChainID; - } - if (filter.escrowAddress && !ethers.isAddress(filter.escrowAddress)) { - throw ErrorInvalidAddress; - } - if (filter.recipient && !ethers.isAddress(filter.recipient)) { - throw ErrorInvalidAddress; - } - - const first = - filter.first !== undefined ? Math.min(filter.first, 1000) : 10; - const skip = filter.skip || 0; - const orderDirection = filter.orderDirection || OrderDirection.DESC; - - const { payouts } = await customGqlFetch<{ payouts: PayoutData[] }>( - getSubgraphUrl(networkData), - GET_PAYOUTS_QUERY(filter), - { - escrowAddress: filter.escrowAddress?.toLowerCase(), - recipient: filter.recipient?.toLowerCase(), - from: filter.from ? getUnixTimestamp(filter.from) : undefined, - to: filter.to ? getUnixTimestamp(filter.to) : undefined, - first: Math.min(first, 1000), - skip, - orderDirection, - }, - options - ); - if (!payouts) { - return []; - } - - return payouts.map((payout) => ({ - id: payout.id, - escrowAddress: payout.escrowAddress, - recipient: payout.recipient, - amount: BigInt(payout.amount), - createdAt: Number(payout.createdAt) * 1000, - })); - } - - /** - * This function returns the cancellation refunds for a given set of networks. - * - * > This uses Subgraph - * - * @param filter - Filter parameters. - * @param options - Optional configuration for subgraph requests. - * @returns List of cancellation refunds matching the filters. - * @throws ErrorUnsupportedChainID If the chain ID is not supported - * @throws ErrorInvalidEscrowAddressProvided If the escrow address is invalid - * @throws ErrorInvalidAddress If the receiver address is invalid - * - * @example - * ```ts - * import { ChainId } from '@human-protocol/sdk'; - * - * const cancellationRefunds = await EscrowUtils.getCancellationRefunds({ - * chainId: ChainId.POLYGON_AMOY, - * escrowAddress: '0x1234567890123456789012345678901234567890', - * }); - * console.log('Cancellation refunds:', cancellationRefunds.length); - * ``` - */ - public static async getCancellationRefunds( - filter: ICancellationRefundFilter, - options?: SubgraphOptions - ): Promise { - const networkData = NETWORKS[filter.chainId]; - if (!networkData) throw ErrorUnsupportedChainID; - if (filter.escrowAddress && !ethers.isAddress(filter.escrowAddress)) { - throw ErrorInvalidEscrowAddressProvided; - } - if (filter.receiver && !ethers.isAddress(filter.receiver)) { - throw ErrorInvalidAddress; - } - - const first = - filter.first !== undefined ? Math.min(filter.first, 1000) : 10; - const skip = filter.skip || 0; - const orderDirection = filter.orderDirection || OrderDirection.DESC; - - const { cancellationRefundEvents } = await customGqlFetch<{ - cancellationRefundEvents: CancellationRefundData[]; - }>( - getSubgraphUrl(networkData), - GET_CANCELLATION_REFUNDS_QUERY(filter), - { - escrowAddress: filter.escrowAddress?.toLowerCase(), - receiver: filter.receiver?.toLowerCase(), - from: filter.from ? getUnixTimestamp(filter.from) : undefined, - to: filter.to ? getUnixTimestamp(filter.to) : undefined, - first, - skip, - orderDirection, - }, - options - ); - - if (!cancellationRefundEvents || cancellationRefundEvents.length === 0) { - return []; - } - - return cancellationRefundEvents.map((event) => ({ - id: event.id, - escrowAddress: event.escrowAddress, - receiver: event.receiver, - amount: BigInt(event.amount), - block: Number(event.block), - timestamp: Number(event.timestamp) * 1000, - txHash: event.txHash, - })); - } - - /** - * This function returns the cancellation refund for a given escrow address. - * - * > This uses Subgraph - * - * @param chainId - Network in which the escrow has been deployed - * @param escrowAddress - Address of the escrow - * @param options - Optional configuration for subgraph requests. - * @returns Cancellation refund data or null if not found. - * @throws ErrorUnsupportedChainID If the chain ID is not supported - * @throws ErrorInvalidEscrowAddressProvided If the escrow address is invalid - * - * @example - * ```ts - * import { ChainId } from '@human-protocol/sdk'; - * - * - * const cancellationRefund = await EscrowUtils.getCancellationRefund( - * ChainId.POLYGON_AMOY, - * "0x1234567890123456789012345678901234567890" - * ); - * if (cancellationRefund) { - * console.log('Refund amount:', cancellationRefund.amount); - * } - * ``` - */ - public static async getCancellationRefund( - chainId: ChainId, - escrowAddress: string, - options?: SubgraphOptions - ): Promise { - const networkData = NETWORKS[chainId]; - if (!networkData) throw ErrorUnsupportedChainID; - - if (!ethers.isAddress(escrowAddress)) { - throw ErrorInvalidEscrowAddressProvided; - } - - const { cancellationRefundEvents } = await customGqlFetch<{ - cancellationRefundEvents: CancellationRefundData[]; - }>( - getSubgraphUrl(networkData), - GET_CANCELLATION_REFUND_BY_ADDRESS_QUERY(), - { escrowAddress: escrowAddress.toLowerCase() }, - options - ); - - if (!cancellationRefundEvents || cancellationRefundEvents.length === 0) { - return null; - } - - return { - id: cancellationRefundEvents[0].id, - escrowAddress: cancellationRefundEvents[0].escrowAddress, - receiver: cancellationRefundEvents[0].receiver, - amount: BigInt(cancellationRefundEvents[0].amount), - block: Number(cancellationRefundEvents[0].block), - timestamp: Number(cancellationRefundEvents[0].timestamp) * 1000, - txHash: cancellationRefundEvents[0].txHash, - }; - } -} - -function mapEscrow(e: EscrowData, chainId: ChainId | number): IEscrow { - return { - id: e.id, - address: e.address, - amountPaid: BigInt(e.amountPaid), - balance: BigInt(e.balance), - count: Number(e.count), - factoryAddress: e.factoryAddress, - finalResultsUrl: e.finalResultsUrl, - finalResultsHash: e.finalResultsHash, - intermediateResultsUrl: e.intermediateResultsUrl, - intermediateResultsHash: e.intermediateResultsHash, - launcher: e.launcher, - jobRequesterId: e.jobRequesterId, - manifestHash: e.manifestHash, - manifest: e.manifest, - recordingOracle: e.recordingOracle, - reputationOracle: e.reputationOracle, - exchangeOracle: e.exchangeOracle, - recordingOracleFee: e.recordingOracleFee - ? Number(e.recordingOracleFee) - : null, - reputationOracleFee: e.reputationOracleFee - ? Number(e.reputationOracleFee) - : null, - exchangeOracleFee: e.exchangeOracleFee ? Number(e.exchangeOracleFee) : null, - status: e.status, - token: e.token, - totalFundedAmount: BigInt(e.totalFundedAmount), - createdAt: Number(e.createdAt) * 1000, - chainId: Number(chainId), - }; -} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/escrow/escrow_utils.ts b/packages/sdk/typescript/human-protocol-sdk/src/escrow/escrow_utils.ts new file mode 100644 index 0000000000..7d3639cdfa --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/escrow/escrow_utils.ts @@ -0,0 +1,510 @@ +import { ethers } from 'ethers'; +import { NETWORKS } from '../constants'; +import { ChainId, OrderDirection } from '../enums'; +import { + ErrorInvalidAddress, + ErrorInvalidEscrowAddressProvided, + ErrorUnsupportedChainID, +} from '../error'; +import { + CancellationRefundData, + EscrowData, + GET_CANCELLATION_REFUNDS_QUERY, + GET_CANCELLATION_REFUND_BY_ADDRESS_QUERY, + GET_ESCROWS_QUERY, + GET_ESCROW_BY_ADDRESS_QUERY, + GET_PAYOUTS_QUERY, + GET_STATUS_UPDATES_QUERY, + PayoutData, + StatusEvent, +} from '../graphql'; +import { + ICancellationRefund, + ICancellationRefundFilter, + IEscrow, + IEscrowsFilter, + IPayout, + IPayoutFilter, + IStatusEvent, + IStatusEventFilter, + SubgraphOptions, +} from '../interfaces'; +import { EscrowStatus } from '../types'; +import { customGqlFetch, getSubgraphUrl, getUnixTimestamp } from '../utils'; +/** + * Utility helpers for escrow-related queries. + * + * @example + * ```ts + * import { ChainId, EscrowUtils } from '@human-protocol/sdk'; + * + * const escrows = await EscrowUtils.getEscrows({ + * chainId: ChainId.POLYGON_AMOY + * }); + * console.log('Escrows:', escrows); + * ``` + */ +export class EscrowUtils { + /** + * This function returns an array of escrows based on the specified filter parameters. + * + * @param filter - Filter parameters. + * @param options - Optional configuration for subgraph requests. + * @returns List of escrows that match the filter. + * @throws ErrorInvalidAddress If any filter address is invalid + * @throws ErrorUnsupportedChainID If the chain ID is not supported + * + * @example + * ```ts + * import { ChainId, EscrowStatus } from '@human-protocol/sdk'; + * + * const filters = { + * status: EscrowStatus.Pending, + * from: new Date(2023, 4, 8), + * to: new Date(2023, 5, 8), + * chainId: ChainId.POLYGON_AMOY + * }; + * const escrows = await EscrowUtils.getEscrows(filters); + * console.log('Found escrows:', escrows.length); + * ``` + */ + public static async getEscrows( + filter: IEscrowsFilter, + options?: SubgraphOptions + ): Promise { + if (filter.launcher && !ethers.isAddress(filter.launcher)) { + throw ErrorInvalidAddress; + } + + if (filter.recordingOracle && !ethers.isAddress(filter.recordingOracle)) { + throw ErrorInvalidAddress; + } + + if (filter.reputationOracle && !ethers.isAddress(filter.reputationOracle)) { + throw ErrorInvalidAddress; + } + + if (filter.exchangeOracle && !ethers.isAddress(filter.exchangeOracle)) { + throw ErrorInvalidAddress; + } + + const first = + filter.first !== undefined ? Math.min(filter.first, 1000) : 10; + const skip = filter.skip || 0; + const orderDirection = filter.orderDirection || OrderDirection.DESC; + + const networkData = NETWORKS[filter.chainId]; + + if (!networkData) { + throw ErrorUnsupportedChainID; + } + + let statuses; + if (filter.status !== undefined) { + statuses = Array.isArray(filter.status) ? filter.status : [filter.status]; + statuses = statuses.map((status) => EscrowStatus[status]); + } + const { escrows } = await customGqlFetch<{ escrows: EscrowData[] }>( + getSubgraphUrl(networkData), + GET_ESCROWS_QUERY(filter), + { + ...filter, + launcher: filter.launcher?.toLowerCase(), + reputationOracle: filter.reputationOracle?.toLowerCase(), + recordingOracle: filter.recordingOracle?.toLowerCase(), + exchangeOracle: filter.exchangeOracle?.toLowerCase(), + status: statuses, + from: filter.from ? getUnixTimestamp(filter.from) : undefined, + to: filter.to ? getUnixTimestamp(filter.to) : undefined, + orderDirection: orderDirection, + first: first, + skip: skip, + }, + options + ); + return (escrows || []).map((e) => mapEscrow(e, networkData.chainId)); + } + + /** + * This function returns the escrow data for a given address. + * + * > This uses Subgraph + * + * @param chainId - Network in which the escrow has been deployed + * @param escrowAddress - Address of the escrow + * @param options - Optional configuration for subgraph requests. + * @returns Escrow data or null if not found. + * @throws ErrorUnsupportedChainID If the chain ID is not supported + * @throws ErrorInvalidAddress If the escrow address is invalid + * + * @example + * ```ts + * import { ChainId } from '@human-protocol/sdk'; + * + * const escrow = await EscrowUtils.getEscrow( + * ChainId.POLYGON_AMOY, + * "0x1234567890123456789012345678901234567890" + * ); + * if (escrow) { + * console.log('Escrow status:', escrow.status); + * } + * ``` + */ + public static async getEscrow( + chainId: ChainId, + escrowAddress: string, + options?: SubgraphOptions + ): Promise { + const networkData = NETWORKS[chainId]; + + if (!networkData) { + throw ErrorUnsupportedChainID; + } + + if (escrowAddress && !ethers.isAddress(escrowAddress)) { + throw ErrorInvalidAddress; + } + + const { escrow } = await customGqlFetch<{ escrow: EscrowData | null }>( + getSubgraphUrl(networkData), + GET_ESCROW_BY_ADDRESS_QUERY(), + { escrowAddress: escrowAddress.toLowerCase() }, + options + ); + if (!escrow) return null; + + return mapEscrow(escrow, networkData.chainId); + } + + /** + * This function returns the status events for a given set of networks within an optional date range. + * + * > This uses Subgraph + * + * @param filter - Filter parameters. + * @param options - Optional configuration for subgraph requests. + * @returns Array of status events with their corresponding statuses. + * @throws ErrorInvalidAddress If the launcher address is invalid + * @throws ErrorUnsupportedChainID If the chain ID is not supported + * + * @example + * ```ts + * import { ChainId, EscrowStatus } from '@human-protocol/sdk'; + * + * const fromDate = new Date('2023-01-01'); + * const toDate = new Date('2023-12-31'); + * const statusEvents = await EscrowUtils.getStatusEvents({ + * chainId: ChainId.POLYGON, + * statuses: [EscrowStatus.Pending, EscrowStatus.Complete], + * from: fromDate, + * to: toDate + * }); + * console.log('Status events:', statusEvents.length); + * ``` + */ + public static async getStatusEvents( + filter: IStatusEventFilter, + options?: SubgraphOptions + ): Promise { + const { + chainId, + statuses, + from, + to, + launcher, + first = 10, + skip = 0, + orderDirection = OrderDirection.DESC, + } = filter; + + if (launcher && !ethers.isAddress(launcher)) { + throw ErrorInvalidAddress; + } + + const networkData = NETWORKS[chainId]; + if (!networkData) { + throw ErrorUnsupportedChainID; + } + + // If statuses are not provided, use all statuses except Launched + const effectiveStatuses = statuses ?? [ + EscrowStatus.Launched, + EscrowStatus.Pending, + EscrowStatus.Partial, + EscrowStatus.Paid, + EscrowStatus.Complete, + EscrowStatus.Cancelled, + ]; + + const statusNames = effectiveStatuses.map((status) => EscrowStatus[status]); + + const data = await customGqlFetch<{ + escrowStatusEvents: StatusEvent[]; + }>( + getSubgraphUrl(networkData), + GET_STATUS_UPDATES_QUERY(from, to, launcher), + { + status: statusNames, + from: from ? getUnixTimestamp(from) : undefined, + to: to ? getUnixTimestamp(to) : undefined, + launcher: launcher || undefined, + orderDirection, + first: Math.min(first, 1000), + skip, + }, + options + ); + + if (!data || !data['escrowStatusEvents']) { + return []; + } + + return data['escrowStatusEvents'].map((event) => ({ + timestamp: Number(event.timestamp) * 1000, + escrowAddress: event.escrowAddress, + status: EscrowStatus[event.status as keyof typeof EscrowStatus], + chainId, + })); + } + + /** + * This function returns the payouts for a given set of networks. + * + * > This uses Subgraph + * + * @param filter - Filter parameters. + * @param options - Optional configuration for subgraph requests. + * @returns List of payouts matching the filters. + * @throws ErrorUnsupportedChainID If the chain ID is not supported + * @throws ErrorInvalidAddress If any filter address is invalid + * + * @example + * ```ts + * import { ChainId } from '@human-protocol/sdk'; + * + * const payouts = await EscrowUtils.getPayouts({ + * chainId: ChainId.POLYGON, + * escrowAddress: '0x1234567890123456789012345678901234567890', + * recipient: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', + * from: new Date('2023-01-01'), + * to: new Date('2023-12-31') + * }); + * console.log('Payouts:', payouts.length); + * ``` + */ + public static async getPayouts( + filter: IPayoutFilter, + options?: SubgraphOptions + ): Promise { + const networkData = NETWORKS[filter.chainId]; + if (!networkData) { + throw ErrorUnsupportedChainID; + } + if (filter.escrowAddress && !ethers.isAddress(filter.escrowAddress)) { + throw ErrorInvalidAddress; + } + if (filter.recipient && !ethers.isAddress(filter.recipient)) { + throw ErrorInvalidAddress; + } + + const first = + filter.first !== undefined ? Math.min(filter.first, 1000) : 10; + const skip = filter.skip || 0; + const orderDirection = filter.orderDirection || OrderDirection.DESC; + + const { payouts } = await customGqlFetch<{ payouts: PayoutData[] }>( + getSubgraphUrl(networkData), + GET_PAYOUTS_QUERY(filter), + { + escrowAddress: filter.escrowAddress?.toLowerCase(), + recipient: filter.recipient?.toLowerCase(), + from: filter.from ? getUnixTimestamp(filter.from) : undefined, + to: filter.to ? getUnixTimestamp(filter.to) : undefined, + first: Math.min(first, 1000), + skip, + orderDirection, + }, + options + ); + if (!payouts) { + return []; + } + + return payouts.map((payout) => ({ + id: payout.id, + escrowAddress: payout.escrowAddress, + recipient: payout.recipient, + amount: BigInt(payout.amount), + createdAt: Number(payout.createdAt) * 1000, + })); + } + + /** + * This function returns the cancellation refunds for a given set of networks. + * + * > This uses Subgraph + * + * @param filter - Filter parameters. + * @param options - Optional configuration for subgraph requests. + * @returns List of cancellation refunds matching the filters. + * @throws ErrorUnsupportedChainID If the chain ID is not supported + * @throws ErrorInvalidEscrowAddressProvided If the escrow address is invalid + * @throws ErrorInvalidAddress If the receiver address is invalid + * + * @example + * ```ts + * import { ChainId } from '@human-protocol/sdk'; + * + * const cancellationRefunds = await EscrowUtils.getCancellationRefunds({ + * chainId: ChainId.POLYGON_AMOY, + * escrowAddress: '0x1234567890123456789012345678901234567890', + * }); + * console.log('Cancellation refunds:', cancellationRefunds.length); + * ``` + */ + public static async getCancellationRefunds( + filter: ICancellationRefundFilter, + options?: SubgraphOptions + ): Promise { + const networkData = NETWORKS[filter.chainId]; + if (!networkData) throw ErrorUnsupportedChainID; + if (filter.escrowAddress && !ethers.isAddress(filter.escrowAddress)) { + throw ErrorInvalidEscrowAddressProvided; + } + if (filter.receiver && !ethers.isAddress(filter.receiver)) { + throw ErrorInvalidAddress; + } + + const first = + filter.first !== undefined ? Math.min(filter.first, 1000) : 10; + const skip = filter.skip || 0; + const orderDirection = filter.orderDirection || OrderDirection.DESC; + + const { cancellationRefundEvents } = await customGqlFetch<{ + cancellationRefundEvents: CancellationRefundData[]; + }>( + getSubgraphUrl(networkData), + GET_CANCELLATION_REFUNDS_QUERY(filter), + { + escrowAddress: filter.escrowAddress?.toLowerCase(), + receiver: filter.receiver?.toLowerCase(), + from: filter.from ? getUnixTimestamp(filter.from) : undefined, + to: filter.to ? getUnixTimestamp(filter.to) : undefined, + first, + skip, + orderDirection, + }, + options + ); + + if (!cancellationRefundEvents || cancellationRefundEvents.length === 0) { + return []; + } + + return cancellationRefundEvents.map((event) => ({ + id: event.id, + escrowAddress: event.escrowAddress, + receiver: event.receiver, + amount: BigInt(event.amount), + block: Number(event.block), + timestamp: Number(event.timestamp) * 1000, + txHash: event.txHash, + })); + } + + /** + * This function returns the cancellation refund for a given escrow address. + * + * > This uses Subgraph + * + * @param chainId - Network in which the escrow has been deployed + * @param escrowAddress - Address of the escrow + * @param options - Optional configuration for subgraph requests. + * @returns Cancellation refund data or null if not found. + * @throws ErrorUnsupportedChainID If the chain ID is not supported + * @throws ErrorInvalidEscrowAddressProvided If the escrow address is invalid + * + * @example + * ```ts + * import { ChainId } from '@human-protocol/sdk'; + * + * + * const cancellationRefund = await EscrowUtils.getCancellationRefund( + * ChainId.POLYGON_AMOY, + * "0x1234567890123456789012345678901234567890" + * ); + * if (cancellationRefund) { + * console.log('Refund amount:', cancellationRefund.amount); + * } + * ``` + */ + public static async getCancellationRefund( + chainId: ChainId, + escrowAddress: string, + options?: SubgraphOptions + ): Promise { + const networkData = NETWORKS[chainId]; + if (!networkData) throw ErrorUnsupportedChainID; + + if (!ethers.isAddress(escrowAddress)) { + throw ErrorInvalidEscrowAddressProvided; + } + + const { cancellationRefundEvents } = await customGqlFetch<{ + cancellationRefundEvents: CancellationRefundData[]; + }>( + getSubgraphUrl(networkData), + GET_CANCELLATION_REFUND_BY_ADDRESS_QUERY(), + { escrowAddress: escrowAddress.toLowerCase() }, + options + ); + + if (!cancellationRefundEvents || cancellationRefundEvents.length === 0) { + return null; + } + + return { + id: cancellationRefundEvents[0].id, + escrowAddress: cancellationRefundEvents[0].escrowAddress, + receiver: cancellationRefundEvents[0].receiver, + amount: BigInt(cancellationRefundEvents[0].amount), + block: Number(cancellationRefundEvents[0].block), + timestamp: Number(cancellationRefundEvents[0].timestamp) * 1000, + txHash: cancellationRefundEvents[0].txHash, + }; + } +} + +function mapEscrow(e: EscrowData, chainId: ChainId | number): IEscrow { + return { + id: e.id, + address: e.address, + amountPaid: BigInt(e.amountPaid), + balance: BigInt(e.balance), + count: Number(e.count), + factoryAddress: e.factoryAddress, + finalResultsUrl: e.finalResultsUrl, + finalResultsHash: e.finalResultsHash, + intermediateResultsUrl: e.intermediateResultsUrl, + intermediateResultsHash: e.intermediateResultsHash, + launcher: e.launcher, + jobRequesterId: e.jobRequesterId, + manifestHash: e.manifestHash, + manifest: e.manifest, + recordingOracle: e.recordingOracle, + reputationOracle: e.reputationOracle, + exchangeOracle: e.exchangeOracle, + recordingOracleFee: e.recordingOracleFee + ? Number(e.recordingOracleFee) + : null, + reputationOracleFee: e.reputationOracleFee + ? Number(e.reputationOracleFee) + : null, + exchangeOracleFee: e.exchangeOracleFee ? Number(e.exchangeOracleFee) : null, + status: e.status, + token: e.token, + totalFundedAmount: BigInt(e.totalFundedAmount), + createdAt: Number(e.createdAt) * 1000, + chainId: Number(chainId), + }; +} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/escrow/index.ts b/packages/sdk/typescript/human-protocol-sdk/src/escrow/index.ts new file mode 100644 index 0000000000..4424ee3911 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/escrow/index.ts @@ -0,0 +1,2 @@ +export { EscrowClient } from './escrow_client'; +export { EscrowUtils } from './escrow_utils'; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/index.ts b/packages/sdk/typescript/human-protocol-sdk/src/index.ts index 0e07f51949..c9cd841e2c 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/index.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/index.ts @@ -1,12 +1,3 @@ -import { StakingClient, StakingUtils } from './staking'; -import { KVStoreClient, KVStoreUtils } from './kvstore'; -import { EscrowClient, EscrowUtils } from './escrow'; -import { StatisticsUtils } from './statistics'; -import { Encryption, EncryptionUtils, MessageDataType } from './encryption'; -import { OperatorUtils } from './operator'; -import { TransactionUtils } from './transaction'; -import { WorkerUtils } from './worker'; - export * from './constants'; export * from './types'; export * from './enums'; @@ -24,18 +15,12 @@ export { InvalidKeyError, } from './error'; -export { - StakingClient, - KVStoreClient, - KVStoreUtils, - EscrowClient, - EscrowUtils, - StatisticsUtils, - Encryption, - EncryptionUtils, - OperatorUtils, - TransactionUtils, - WorkerUtils, - StakingUtils, - MessageDataType, -}; +export { StakingClient, StakingUtils } from './staking'; +export { KVStoreClient, KVStoreUtils } from './kvstore'; +export { EscrowClient, EscrowUtils } from './escrow'; +export { StatisticsUtils } from './statistics'; +export { Encryption, EncryptionUtils } from './encryption'; +export type { MessageDataType } from './encryption'; +export { OperatorUtils } from './operator'; +export { TransactionUtils } from './transaction'; +export { WorkerUtils } from './worker'; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/kvstore/index.ts b/packages/sdk/typescript/human-protocol-sdk/src/kvstore/index.ts new file mode 100644 index 0000000000..507a0f6d62 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/kvstore/index.ts @@ -0,0 +1,2 @@ +export { KVStoreClient } from './kvstore_client'; +export { KVStoreUtils } from './kvstore_utils'; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts b/packages/sdk/typescript/human-protocol-sdk/src/kvstore/kvstore_client.ts similarity index 53% rename from packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts rename to packages/sdk/typescript/human-protocol-sdk/src/kvstore/kvstore_client.ts index 22635167d3..763028c38f 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/kvstore/kvstore_client.ts @@ -3,28 +3,20 @@ import { KVStore__factory, } from '@human-protocol/core/typechain-types'; import { ContractRunner, ethers } from 'ethers'; -import { BaseEthersClient } from './base'; -import { KVStoreKeys, NETWORKS } from './constants'; -import { requiresSigner } from './decorators'; -import { ChainId } from './enums'; +import { BaseEthersClient } from '../base'; +import { NETWORKS } from '../constants'; +import { requiresSigner } from '../decorators'; +import { ChainId } from '../enums'; import { ErrorInvalidAddress, - ErrorInvalidHash, ErrorInvalidUrl, ErrorKVStoreArrayLength, ErrorKVStoreEmptyKey, ErrorProviderDoesNotExist, ErrorUnsupportedChainID, - InvalidKeyError, -} from './error'; -import { NetworkData, TransactionOverrides } from './types'; -import { getSubgraphUrl, customGqlFetch, isValidUrl } from './utils'; -import { - GET_KVSTORE_BY_ADDRESS_AND_KEY_QUERY, - GET_KVSTORE_BY_ADDRESS_QUERY, -} from './graphql/queries/kvstore'; -import { KVStoreData } from './graphql'; -import { IKVStore, SubgraphOptions } from './interfaces'; +} from '../error'; +import { NetworkData, TransactionOverrides } from '../types'; +import { isValidUrl } from '../utils'; /** * Client for interacting with the KVStore contract. * @@ -282,224 +274,3 @@ export class KVStoreClient extends BaseEthersClient { } } } - -/** - * Utility helpers for KVStore-related queries. - * - * @example - * ```ts - * import { ChainId, KVStoreUtils } from '@human-protocol/sdk'; - * - * const kvStoreData = await KVStoreUtils.getKVStoreData( - * ChainId.POLYGON_AMOY, - * "0x1234567890123456789012345678901234567890" - * ); - * console.log('KVStore data:', kvStoreData); - * ``` - */ -export class KVStoreUtils { - /** - * This function returns the KVStore data for a given address. - * - * @param chainId - Network in which the KVStore is deployed - * @param address - Address of the KVStore - * @param options - Optional configuration for subgraph requests. - * @returns KVStore data - * @throws ErrorUnsupportedChainID If the network's chainId is not supported - * @throws ErrorInvalidAddress If the address is invalid - * - * @example - * ```ts - * const kvStoreData = await KVStoreUtils.getKVStoreData( - * ChainId.POLYGON_AMOY, - * "0x1234567890123456789012345678901234567890" - * ); - * console.log('KVStore data:', kvStoreData); - * ``` - */ - public static async getKVStoreData( - chainId: ChainId, - address: string, - options?: SubgraphOptions - ): Promise { - const networkData = NETWORKS[chainId]; - - if (!networkData) { - throw ErrorUnsupportedChainID; - } - - if (address && !ethers.isAddress(address)) { - throw ErrorInvalidAddress; - } - - const { kvstores } = await customGqlFetch<{ kvstores: KVStoreData[] }>( - getSubgraphUrl(networkData), - GET_KVSTORE_BY_ADDRESS_QUERY(), - { address: address.toLowerCase() }, - options - ); - - const kvStoreData = kvstores.map((item) => ({ - key: item.key, - value: item.value, - })); - - return kvStoreData || []; - } - - /** - * Gets the value of a key-value pair in the KVStore using the subgraph. - * - * @param chainId - Network in which the KVStore is deployed - * @param address - Address from which to get the key value. - * @param key - Key to obtain the value. - * @param options - Optional configuration for subgraph requests. - * @returns Value of the key. - * @throws ErrorUnsupportedChainID If the network's chainId is not supported - * @throws ErrorInvalidAddress If the address is invalid - * @throws ErrorKVStoreEmptyKey If the key is empty - * @throws InvalidKeyError If the key is not found - * - * @example - * ```ts - * const value = await KVStoreUtils.get( - * ChainId.POLYGON_AMOY, - * '0x1234567890123456789012345678901234567890', - * 'role' - * ); - * console.log('Value:', value); - * ``` - */ - public static async get( - chainId: ChainId, - address: string, - key: string, - options?: SubgraphOptions - ): Promise { - if (key === '') throw ErrorKVStoreEmptyKey; - if (!ethers.isAddress(address)) throw ErrorInvalidAddress; - - const networkData = NETWORKS[chainId]; - - if (!networkData) { - throw ErrorUnsupportedChainID; - } - - const { kvstores } = await customGqlFetch<{ kvstores: KVStoreData[] }>( - getSubgraphUrl(networkData), - GET_KVSTORE_BY_ADDRESS_AND_KEY_QUERY(), - { address: address.toLowerCase(), key }, - options - ); - - if (!kvstores || kvstores.length === 0) { - throw new InvalidKeyError(key, address); - } - - return kvstores[0].value; - } - - /** - * Gets the URL value of the given entity, and verifies its hash. - * - * @param chainId - Network in which the KVStore is deployed - * @param address - Address from which to get the URL value. - * @param urlKey - Configurable URL key. `url` by default. - * @param options - Optional configuration for subgraph requests. - * @returns URL value for the given address if it exists, and the content is valid - * @throws ErrorInvalidAddress If the address is invalid - * @throws ErrorInvalidHash If the hash verification fails - * @throws Error If fetching URL or hash fails - * - * @example - * ```ts - * const url = await KVStoreUtils.getFileUrlAndVerifyHash( - * ChainId.POLYGON_AMOY, - * '0x1234567890123456789012345678901234567890' - * ); - * console.log('Verified URL:', url); - * ``` - */ - public static async getFileUrlAndVerifyHash( - chainId: ChainId, - address: string, - urlKey = 'url', - options?: SubgraphOptions - ): Promise { - if (!ethers.isAddress(address)) throw ErrorInvalidAddress; - const hashKey = urlKey + '_hash'; - - let url = '', - hash = ''; - - try { - url = await this.get(chainId, address, urlKey, options); - } catch (e) { - if (e instanceof Error) throw Error(`Failed to get URL: ${e.message}`); - } - - // Return empty string - if (!url?.length) { - return ''; - } - - try { - hash = await this.get(chainId, address, hashKey); - } catch (e) { - if (e instanceof Error) throw Error(`Failed to get Hash: ${e.message}`); - } - - const content = await fetch(url).then((res) => res.text()); - const contentHash = ethers.keccak256(ethers.toUtf8Bytes(content)); - - const formattedHash = hash?.replace(/^0x/, ''); - const formattedContentHash = contentHash?.replace(/^0x/, ''); - - if (formattedHash !== formattedContentHash) { - throw ErrorInvalidHash; - } - - return url; - } - - /** - * Gets the public key of the given entity, and verifies its hash. - * - * @param chainId - Network in which the KVStore is deployed - * @param address - Address from which to get the public key. - * @param options - Optional configuration for subgraph requests. - * @returns Public key for the given address if it exists, and the content is valid - * @throws ErrorInvalidAddress If the address is invalid - * @throws ErrorInvalidHash If the hash verification fails - * @throws Error If fetching the public key fails - * - * @example - * ```ts - * const publicKey = await KVStoreUtils.getPublicKey( - * ChainId.POLYGON_AMOY, - * '0x1234567890123456789012345678901234567890' - * ); - * console.log('Public key:', publicKey); - * ``` - */ - public static async getPublicKey( - chainId: ChainId, - address: string, - options?: SubgraphOptions - ): Promise { - const publicKeyUrl = await this.getFileUrlAndVerifyHash( - chainId, - address, - KVStoreKeys.publicKey, - options - ); - - if (publicKeyUrl === '') { - return ''; - } - - const publicKey = await fetch(publicKeyUrl).then((res) => res.text()); - - return publicKey; - } -} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/kvstore/kvstore_utils.ts b/packages/sdk/typescript/human-protocol-sdk/src/kvstore/kvstore_utils.ts new file mode 100644 index 0000000000..ecc9097a40 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/kvstore/kvstore_utils.ts @@ -0,0 +1,237 @@ +import { ethers } from 'ethers'; +import { KVStoreKeys, NETWORKS } from '../constants'; +import { ChainId } from '../enums'; +import { + ErrorInvalidAddress, + ErrorInvalidHash, + ErrorKVStoreEmptyKey, + ErrorUnsupportedChainID, + InvalidKeyError, +} from '../error'; +import { KVStoreData } from '../graphql'; +import { + GET_KVSTORE_BY_ADDRESS_AND_KEY_QUERY, + GET_KVSTORE_BY_ADDRESS_QUERY, +} from '../graphql/queries/kvstore'; +import { IKVStore, SubgraphOptions } from '../interfaces'; +import { customGqlFetch, getSubgraphUrl } from '../utils'; +/** + * Utility helpers for KVStore-related queries. + * + * @example + * ```ts + * import { ChainId, KVStoreUtils } from '@human-protocol/sdk'; + * + * const kvStoreData = await KVStoreUtils.getKVStoreData( + * ChainId.POLYGON_AMOY, + * "0x1234567890123456789012345678901234567890" + * ); + * console.log('KVStore data:', kvStoreData); + * ``` + */ +export class KVStoreUtils { + /** + * This function returns the KVStore data for a given address. + * + * @param chainId - Network in which the KVStore is deployed + * @param address - Address of the KVStore + * @param options - Optional configuration for subgraph requests. + * @returns KVStore data + * @throws ErrorUnsupportedChainID If the network's chainId is not supported + * @throws ErrorInvalidAddress If the address is invalid + * + * @example + * ```ts + * const kvStoreData = await KVStoreUtils.getKVStoreData( + * ChainId.POLYGON_AMOY, + * "0x1234567890123456789012345678901234567890" + * ); + * console.log('KVStore data:', kvStoreData); + * ``` + */ + public static async getKVStoreData( + chainId: ChainId, + address: string, + options?: SubgraphOptions + ): Promise { + const networkData = NETWORKS[chainId]; + + if (!networkData) { + throw ErrorUnsupportedChainID; + } + + if (address && !ethers.isAddress(address)) { + throw ErrorInvalidAddress; + } + + const { kvstores } = await customGqlFetch<{ kvstores: KVStoreData[] }>( + getSubgraphUrl(networkData), + GET_KVSTORE_BY_ADDRESS_QUERY(), + { address: address.toLowerCase() }, + options + ); + + const kvStoreData = kvstores.map((item) => ({ + key: item.key, + value: item.value, + })); + + return kvStoreData || []; + } + + /** + * Gets the value of a key-value pair in the KVStore using the subgraph. + * + * @param chainId - Network in which the KVStore is deployed + * @param address - Address from which to get the key value. + * @param key - Key to obtain the value. + * @param options - Optional configuration for subgraph requests. + * @returns Value of the key. + * @throws ErrorUnsupportedChainID If the network's chainId is not supported + * @throws ErrorInvalidAddress If the address is invalid + * @throws ErrorKVStoreEmptyKey If the key is empty + * @throws InvalidKeyError If the key is not found + * + * @example + * ```ts + * const value = await KVStoreUtils.get( + * ChainId.POLYGON_AMOY, + * '0x1234567890123456789012345678901234567890', + * 'role' + * ); + * console.log('Value:', value); + * ``` + */ + public static async get( + chainId: ChainId, + address: string, + key: string, + options?: SubgraphOptions + ): Promise { + if (key === '') throw ErrorKVStoreEmptyKey; + if (!ethers.isAddress(address)) throw ErrorInvalidAddress; + + const networkData = NETWORKS[chainId]; + + if (!networkData) { + throw ErrorUnsupportedChainID; + } + + const { kvstores } = await customGqlFetch<{ kvstores: KVStoreData[] }>( + getSubgraphUrl(networkData), + GET_KVSTORE_BY_ADDRESS_AND_KEY_QUERY(), + { address: address.toLowerCase(), key }, + options + ); + + if (!kvstores || kvstores.length === 0) { + throw new InvalidKeyError(key, address); + } + + return kvstores[0].value; + } + + /** + * Gets the URL value of the given entity, and verifies its hash. + * + * @param chainId - Network in which the KVStore is deployed + * @param address - Address from which to get the URL value. + * @param urlKey - Configurable URL key. `url` by default. + * @param options - Optional configuration for subgraph requests. + * @returns URL value for the given address if it exists, and the content is valid + * @throws ErrorInvalidAddress If the address is invalid + * @throws ErrorInvalidHash If the hash verification fails + * @throws Error If fetching URL or hash fails + * + * @example + * ```ts + * const url = await KVStoreUtils.getFileUrlAndVerifyHash( + * ChainId.POLYGON_AMOY, + * '0x1234567890123456789012345678901234567890' + * ); + * console.log('Verified URL:', url); + * ``` + */ + public static async getFileUrlAndVerifyHash( + chainId: ChainId, + address: string, + urlKey = 'url', + options?: SubgraphOptions + ): Promise { + if (!ethers.isAddress(address)) throw ErrorInvalidAddress; + const hashKey = urlKey + '_hash'; + + let url = '', + hash = ''; + + try { + url = await this.get(chainId, address, urlKey, options); + } catch (e) { + if (e instanceof Error) throw Error(`Failed to get URL: ${e.message}`); + } + + // Return empty string + if (!url?.length) { + return ''; + } + + try { + hash = await this.get(chainId, address, hashKey); + } catch (e) { + if (e instanceof Error) throw Error(`Failed to get Hash: ${e.message}`); + } + + const content = await fetch(url).then((res) => res.text()); + const contentHash = ethers.keccak256(ethers.toUtf8Bytes(content)); + + const formattedHash = hash?.replace(/^0x/, ''); + const formattedContentHash = contentHash?.replace(/^0x/, ''); + + if (formattedHash !== formattedContentHash) { + throw ErrorInvalidHash; + } + + return url; + } + + /** + * Gets the public key of the given entity, and verifies its hash. + * + * @param chainId - Network in which the KVStore is deployed + * @param address - Address from which to get the public key. + * @param options - Optional configuration for subgraph requests. + * @returns Public key for the given address if it exists, and the content is valid + * @throws ErrorInvalidAddress If the address is invalid + * @throws ErrorInvalidHash If the hash verification fails + * @throws Error If fetching the public key fails + * + * @example + * ```ts + * const publicKey = await KVStoreUtils.getPublicKey( + * ChainId.POLYGON_AMOY, + * '0x1234567890123456789012345678901234567890' + * ); + * console.log('Public key:', publicKey); + * ``` + */ + public static async getPublicKey( + chainId: ChainId, + address: string, + options?: SubgraphOptions + ): Promise { + const publicKeyUrl = await this.getFileUrlAndVerifyHash( + chainId, + address, + KVStoreKeys.publicKey, + options + ); + + if (publicKeyUrl === '') { + return ''; + } + + const publicKey = await fetch(publicKeyUrl).then((res) => res.text()); + + return publicKey; + } +} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/operator/index.ts b/packages/sdk/typescript/human-protocol-sdk/src/operator/index.ts new file mode 100644 index 0000000000..9cb7c0df41 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/operator/index.ts @@ -0,0 +1 @@ +export { OperatorUtils } from './operator_utils'; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/operator.ts b/packages/sdk/typescript/human-protocol-sdk/src/operator/operator_utils.ts similarity index 96% rename from packages/sdk/typescript/human-protocol-sdk/src/operator.ts rename to packages/sdk/typescript/human-protocol-sdk/src/operator/operator_utils.ts index ad0dd150d7..b7a84b9668 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/operator.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/operator/operator_utils.ts @@ -4,27 +4,27 @@ import { IOperatorsFilter, IReward, SubgraphOptions, -} from './interfaces'; -import { GET_REWARD_ADDED_EVENTS_QUERY } from './graphql/queries/reward'; +} from '../interfaces'; +import { GET_REWARD_ADDED_EVENTS_QUERY } from '../graphql/queries/reward'; import { IOperatorSubgraph, IReputationNetworkSubgraph, RewardAddedEventData, -} from './graphql'; +} from '../graphql'; import { GET_LEADER_QUERY, GET_LEADERS_QUERY, GET_REPUTATION_NETWORK_QUERY, -} from './graphql/queries/operator'; +} from '../graphql/queries/operator'; import { ethers } from 'ethers'; import { ErrorInvalidSlasherAddressProvided, ErrorInvalidStakerAddressProvided, ErrorUnsupportedChainID, -} from './error'; -import { getSubgraphUrl, customGqlFetch } from './utils'; -import { ChainId, OrderDirection } from './enums'; -import { NETWORKS } from './constants'; +} from '../error'; +import { getSubgraphUrl, customGqlFetch } from '../utils'; +import { ChainId, OrderDirection } from '../enums'; +import { NETWORKS } from '../constants'; /** * Utility helpers for operator-related queries. diff --git a/packages/sdk/typescript/human-protocol-sdk/src/staking/index.ts b/packages/sdk/typescript/human-protocol-sdk/src/staking/index.ts new file mode 100644 index 0000000000..714d1b4561 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/staking/index.ts @@ -0,0 +1,2 @@ +export { StakingClient } from './staking_client'; +export { StakingUtils } from './staking_utils'; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/staking.ts b/packages/sdk/typescript/human-protocol-sdk/src/staking/staking_client.ts similarity index 71% rename from packages/sdk/typescript/human-protocol-sdk/src/staking.ts rename to packages/sdk/typescript/human-protocol-sdk/src/staking/staking_client.ts index b0f0284db6..65c747140a 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/staking.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/staking/staking_client.ts @@ -7,10 +7,10 @@ import { Staking__factory, } from '@human-protocol/core/typechain-types'; import { ContractRunner, ethers } from 'ethers'; -import { BaseEthersClient } from './base'; -import { NETWORKS } from './constants'; -import { requiresSigner } from './decorators'; -import { ChainId, OrderDirection } from './enums'; +import { BaseEthersClient } from '../base'; +import { NETWORKS } from '../constants'; +import { requiresSigner } from '../decorators'; +import { ChainId } from '../enums'; import { ErrorEscrowAddressIsNotProvidedByFactory, ErrorInvalidEscrowAddressProvided, @@ -19,22 +19,11 @@ import { ErrorInvalidStakingValueSign, ErrorInvalidStakingValueType, ErrorProviderDoesNotExist, - ErrorStakerNotFound, ErrorUnsupportedChainID, -} from './error'; -import { - IStaker, - IStakersFilter, - StakerInfo, - SubgraphOptions, -} from './interfaces'; -import { StakerData } from './graphql'; -import { NetworkData, TransactionOverrides } from './types'; -import { getSubgraphUrl, customGqlFetch, throwError } from './utils'; -import { - GET_STAKER_BY_ADDRESS_QUERY, - GET_STAKERS_QUERY, -} from './graphql/queries/staking'; +} from '../error'; +import { StakerInfo } from '../interfaces'; +import { NetworkData, TransactionOverrides } from '../types'; +import { throwError } from '../utils'; /** * Client for staking actions on HUMAN Protocol. @@ -456,158 +445,3 @@ export class StakingClient extends BaseEthersClient { } } } - -/** - * Utility helpers for Staking-related queries. - * - * @example - * ```ts - * import { StakingUtils, ChainId } from '@human-protocol/sdk'; - * - * const staker = await StakingUtils.getStaker( - * ChainId.POLYGON_AMOY, - * '0xYourStakerAddress' - * ); - * console.log('Staked amount:', staker.stakedAmount); - * ``` - */ -export class StakingUtils { - /** - * Gets staking info for a staker from the subgraph. - * - * @param chainId - Network in which the staking contract is deployed - * @param stakerAddress - Address of the staker - * @param options - Optional configuration for subgraph requests. - * @returns Staker info from subgraph - * @throws ErrorInvalidStakerAddressProvided If the staker address is invalid - * @throws ErrorUnsupportedChainID If the chain ID is not supported - * @throws ErrorStakerNotFound If the staker is not found - * - * @example - * ```ts - * import { StakingUtils, ChainId } from '@human-protocol/sdk'; - * - * const staker = await StakingUtils.getStaker( - * ChainId.POLYGON_AMOY, - * '0xYourStakerAddress' - * ); - * console.log('Staked amount:', staker.stakedAmount); - * ``` - */ - public static async getStaker( - chainId: ChainId, - stakerAddress: string, - options?: SubgraphOptions - ): Promise { - if (!ethers.isAddress(stakerAddress)) { - throw ErrorInvalidStakerAddressProvided; - } - - const networkData: NetworkData | undefined = NETWORKS[chainId]; - if (!networkData) { - throw ErrorUnsupportedChainID; - } - - const { staker } = await customGqlFetch<{ staker: StakerData }>( - getSubgraphUrl(networkData), - GET_STAKER_BY_ADDRESS_QUERY, - { id: stakerAddress.toLowerCase() }, - options - ); - - if (!staker) { - throw ErrorStakerNotFound; - } - - return mapStaker(staker); - } - - /** - * Gets all stakers from the subgraph with filters, pagination and ordering. - * - * @param filter - Stakers filter with pagination and ordering - * @param options - Optional configuration for subgraph requests. - * @returns Array of stakers - * @throws ErrorUnsupportedChainID If the chain ID is not supported - * - * @example - * ```ts - * import { ChainId } from '@human-protocol/sdk'; - * - * const filter = { - * chainId: ChainId.POLYGON_AMOY, - * minStakedAmount: '1000000000000000000', // 1 token in WEI - * }; - * const stakers = await StakingUtils.getStakers(filter); - * console.log('Stakers:', stakers.length); - * ``` - */ - public static async getStakers( - filter: IStakersFilter, - options?: SubgraphOptions - ): Promise { - const first = - filter.first !== undefined ? Math.min(filter.first, 1000) : 10; - const skip = filter.skip || 0; - const orderDirection = filter.orderDirection || OrderDirection.DESC; - const orderBy = filter.orderBy || 'lastDepositTimestamp'; - - const networkData = NETWORKS[filter.chainId]; - if (!networkData) { - throw ErrorUnsupportedChainID; - } - - const { stakers } = await customGqlFetch<{ stakers: StakerData[] }>( - getSubgraphUrl(networkData), - GET_STAKERS_QUERY(filter), - { - minStakedAmount: filter.minStakedAmount - ? filter.minStakedAmount - : undefined, - maxStakedAmount: filter.maxStakedAmount - ? filter.maxStakedAmount - : undefined, - minLockedAmount: filter.minLockedAmount - ? filter.minLockedAmount - : undefined, - maxLockedAmount: filter.maxLockedAmount - ? filter.maxLockedAmount - : undefined, - minWithdrawnAmount: filter.minWithdrawnAmount - ? filter.minWithdrawnAmount - : undefined, - maxWithdrawnAmount: filter.maxWithdrawnAmount - ? filter.maxWithdrawnAmount - : undefined, - minSlashedAmount: filter.minSlashedAmount - ? filter.minSlashedAmount - : undefined, - maxSlashedAmount: filter.maxSlashedAmount - ? filter.maxSlashedAmount - : undefined, - orderBy: orderBy, - orderDirection: orderDirection, - first: first, - skip: skip, - }, - options - ); - if (!stakers) { - return []; - } - - return stakers.map((s) => mapStaker(s)); - } -} - -function mapStaker(s: StakerData): IStaker { - return { - address: s.address, - stakedAmount: BigInt(s.stakedAmount), - lockedAmount: BigInt(s.lockedAmount), - withdrawableAmount: BigInt(s.withdrawnAmount), - slashedAmount: BigInt(s.slashedAmount), - lockedUntil: Number(s.lockedUntilTimestamp) * 1000, - lastDepositTimestamp: Number(s.lastDepositTimestamp) * 1000, - }; -} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/staking/staking_utils.ts b/packages/sdk/typescript/human-protocol-sdk/src/staking/staking_utils.ts new file mode 100644 index 0000000000..3c17984bd7 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/staking/staking_utils.ts @@ -0,0 +1,170 @@ +import { ethers } from 'ethers'; +import { NETWORKS } from '../constants'; +import { ChainId, OrderDirection } from '../enums'; +import { + ErrorInvalidStakerAddressProvided, + ErrorStakerNotFound, + ErrorUnsupportedChainID, +} from '../error'; +import { StakerData } from '../graphql'; +import { + GET_STAKER_BY_ADDRESS_QUERY, + GET_STAKERS_QUERY, +} from '../graphql/queries/staking'; +import { IStaker, IStakersFilter, SubgraphOptions } from '../interfaces'; +import { NetworkData } from '../types'; +import { customGqlFetch, getSubgraphUrl } from '../utils'; +/** + * Utility helpers for Staking-related queries. + * + * @example + * ```ts + * import { StakingUtils, ChainId } from '@human-protocol/sdk'; + * + * const staker = await StakingUtils.getStaker( + * ChainId.POLYGON_AMOY, + * '0xYourStakerAddress' + * ); + * console.log('Staked amount:', staker.stakedAmount); + * ``` + */ +export class StakingUtils { + /** + * Gets staking info for a staker from the subgraph. + * + * @param chainId - Network in which the staking contract is deployed + * @param stakerAddress - Address of the staker + * @param options - Optional configuration for subgraph requests. + * @returns Staker info from subgraph + * @throws ErrorInvalidStakerAddressProvided If the staker address is invalid + * @throws ErrorUnsupportedChainID If the chain ID is not supported + * @throws ErrorStakerNotFound If the staker is not found + * + * @example + * ```ts + * import { StakingUtils, ChainId } from '@human-protocol/sdk'; + * + * const staker = await StakingUtils.getStaker( + * ChainId.POLYGON_AMOY, + * '0xYourStakerAddress' + * ); + * console.log('Staked amount:', staker.stakedAmount); + * ``` + */ + public static async getStaker( + chainId: ChainId, + stakerAddress: string, + options?: SubgraphOptions + ): Promise { + if (!ethers.isAddress(stakerAddress)) { + throw ErrorInvalidStakerAddressProvided; + } + + const networkData: NetworkData | undefined = NETWORKS[chainId]; + if (!networkData) { + throw ErrorUnsupportedChainID; + } + + const { staker } = await customGqlFetch<{ staker: StakerData }>( + getSubgraphUrl(networkData), + GET_STAKER_BY_ADDRESS_QUERY, + { id: stakerAddress.toLowerCase() }, + options + ); + + if (!staker) { + throw ErrorStakerNotFound; + } + + return mapStaker(staker); + } + + /** + * Gets all stakers from the subgraph with filters, pagination and ordering. + * + * @param filter - Stakers filter with pagination and ordering + * @param options - Optional configuration for subgraph requests. + * @returns Array of stakers + * @throws ErrorUnsupportedChainID If the chain ID is not supported + * + * @example + * ```ts + * import { ChainId } from '@human-protocol/sdk'; + * + * const filter = { + * chainId: ChainId.POLYGON_AMOY, + * minStakedAmount: '1000000000000000000', // 1 token in WEI + * }; + * const stakers = await StakingUtils.getStakers(filter); + * console.log('Stakers:', stakers.length); + * ``` + */ + public static async getStakers( + filter: IStakersFilter, + options?: SubgraphOptions + ): Promise { + const first = + filter.first !== undefined ? Math.min(filter.first, 1000) : 10; + const skip = filter.skip || 0; + const orderDirection = filter.orderDirection || OrderDirection.DESC; + const orderBy = filter.orderBy || 'lastDepositTimestamp'; + + const networkData = NETWORKS[filter.chainId]; + if (!networkData) { + throw ErrorUnsupportedChainID; + } + + const { stakers } = await customGqlFetch<{ stakers: StakerData[] }>( + getSubgraphUrl(networkData), + GET_STAKERS_QUERY(filter), + { + minStakedAmount: filter.minStakedAmount + ? filter.minStakedAmount + : undefined, + maxStakedAmount: filter.maxStakedAmount + ? filter.maxStakedAmount + : undefined, + minLockedAmount: filter.minLockedAmount + ? filter.minLockedAmount + : undefined, + maxLockedAmount: filter.maxLockedAmount + ? filter.maxLockedAmount + : undefined, + minWithdrawnAmount: filter.minWithdrawnAmount + ? filter.minWithdrawnAmount + : undefined, + maxWithdrawnAmount: filter.maxWithdrawnAmount + ? filter.maxWithdrawnAmount + : undefined, + minSlashedAmount: filter.minSlashedAmount + ? filter.minSlashedAmount + : undefined, + maxSlashedAmount: filter.maxSlashedAmount + ? filter.maxSlashedAmount + : undefined, + orderBy: orderBy, + orderDirection: orderDirection, + first: first, + skip: skip, + }, + options + ); + if (!stakers) { + return []; + } + + return stakers.map((s) => mapStaker(s)); + } +} + +function mapStaker(s: StakerData): IStaker { + return { + address: s.address, + stakedAmount: BigInt(s.stakedAmount), + lockedAmount: BigInt(s.lockedAmount), + withdrawableAmount: BigInt(s.withdrawnAmount), + slashedAmount: BigInt(s.slashedAmount), + lockedUntil: Number(s.lockedUntilTimestamp) * 1000, + lastDepositTimestamp: Number(s.lastDepositTimestamp) * 1000, + }; +} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/statistics/index.ts b/packages/sdk/typescript/human-protocol-sdk/src/statistics/index.ts new file mode 100644 index 0000000000..9e5d6b2493 --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/statistics/index.ts @@ -0,0 +1 @@ +export { StatisticsUtils } from './statistics_utils'; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts b/packages/sdk/typescript/human-protocol-sdk/src/statistics/statistics_utils.ts similarity index 99% rename from packages/sdk/typescript/human-protocol-sdk/src/statistics.ts rename to packages/sdk/typescript/human-protocol-sdk/src/statistics/statistics_utils.ts index fd1192a449..1cf4b4422c 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/statistics/statistics_utils.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { OrderDirection } from './enums'; +import { OrderDirection } from '../enums'; import { EscrowStatisticsData, EventDayData, @@ -9,7 +9,7 @@ import { GET_HOLDERS_QUERY, HMTHolderData, HMTStatisticsData, -} from './graphql'; +} from '../graphql'; import { IDailyHMT, IEscrowStatistics, @@ -20,14 +20,14 @@ import { IStatisticsFilter, IWorkerStatistics, SubgraphOptions, -} from './interfaces'; -import { NetworkData } from './types'; +} from '../interfaces'; +import { NetworkData } from '../types'; import { getSubgraphUrl, getUnixTimestamp, customGqlFetch, throwError, -} from './utils'; +} from '../utils'; /** * Utility class for statistics-related queries. diff --git a/packages/sdk/typescript/human-protocol-sdk/src/transaction/index.ts b/packages/sdk/typescript/human-protocol-sdk/src/transaction/index.ts new file mode 100644 index 0000000000..d4461b9f3f --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/transaction/index.ts @@ -0,0 +1 @@ +export { TransactionUtils } from './transaction_utils'; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts b/packages/sdk/typescript/human-protocol-sdk/src/transaction/transaction_utils.ts similarity index 97% rename from packages/sdk/typescript/human-protocol-sdk/src/transaction.ts rename to packages/sdk/typescript/human-protocol-sdk/src/transaction/transaction_utils.ts index d2802feff7..d055ef27c4 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/transaction/transaction_utils.ts @@ -1,23 +1,23 @@ import { ethers } from 'ethers'; -import { NETWORKS } from './constants'; -import { ChainId, OrderDirection } from './enums'; +import { NETWORKS } from '../constants'; +import { ChainId, OrderDirection } from '../enums'; import { ErrorCannotUseDateAndBlockSimultaneously, ErrorInvalidHashProvided, ErrorUnsupportedChainID, -} from './error'; -import { TransactionData } from './graphql'; +} from '../error'; +import { TransactionData } from '../graphql'; import { GET_TRANSACTION_QUERY, GET_TRANSACTIONS_QUERY, -} from './graphql/queries/transaction'; +} from '../graphql/queries/transaction'; import { InternalTransaction, ITransaction, ITransactionsFilter, SubgraphOptions, -} from './interfaces'; -import { getSubgraphUrl, getUnixTimestamp, customGqlFetch } from './utils'; +} from '../interfaces'; +import { getSubgraphUrl, getUnixTimestamp, customGqlFetch } from '../utils'; /** * Utility class for transaction-related queries. diff --git a/packages/sdk/typescript/human-protocol-sdk/src/worker/index.ts b/packages/sdk/typescript/human-protocol-sdk/src/worker/index.ts new file mode 100644 index 0000000000..d6dfa1ca7b --- /dev/null +++ b/packages/sdk/typescript/human-protocol-sdk/src/worker/index.ts @@ -0,0 +1 @@ +export { WorkerUtils } from './worker_utils'; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/worker.ts b/packages/sdk/typescript/human-protocol-sdk/src/worker/worker_utils.ts similarity index 91% rename from packages/sdk/typescript/human-protocol-sdk/src/worker.ts rename to packages/sdk/typescript/human-protocol-sdk/src/worker/worker_utils.ts index e76278bd3b..d26c847f8d 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/worker.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/worker/worker_utils.ts @@ -1,11 +1,11 @@ import { ethers } from 'ethers'; -import { NETWORKS } from './constants'; -import { ChainId, OrderDirection } from './enums'; -import { ErrorInvalidAddress, ErrorUnsupportedChainID } from './error'; -import { WorkerData } from './graphql'; -import { GET_WORKER_QUERY, GET_WORKERS_QUERY } from './graphql/queries/worker'; -import { IWorker, IWorkersFilter, SubgraphOptions } from './interfaces'; -import { getSubgraphUrl, customGqlFetch } from './utils'; +import { NETWORKS } from '../constants'; +import { ChainId, OrderDirection } from '../enums'; +import { ErrorInvalidAddress, ErrorUnsupportedChainID } from '../error'; +import { WorkerData } from '../graphql'; +import { GET_WORKER_QUERY, GET_WORKERS_QUERY } from '../graphql/queries/worker'; +import { IWorker, IWorkersFilter, SubgraphOptions } from '../interfaces'; +import { getSubgraphUrl, customGqlFetch } from '../utils'; /** * Utility class for worker-related operations.