Skip to content
This repository has been archived by the owner on Dec 13, 2024. It is now read-only.

Add support for estimation #116

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 32 additions & 1 deletion src/DripsSubgraph/DripsSubgraphClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,37 @@ export default class DripsSubgraphClient {
return response?.data?.dripsSetEvents?.map(mapDripsSetEventToDto) || [];
}

/**
* Returns a list of `DripsSet` events for the given receiver.
* @param {string} receiverId The receiver's user ID.
* @param {number} skip The number of database entries to skip. Defaults to `0`.
* @param {number} first The number of database entries to take. Defaults to `100`.
* @returns A `Promise` which resolves to the user's `DripsSet` events.
* @throws {@link DripsErrors.argumentMissingError} if the `receiverId` is missing.
* @throws {@link DripsErrors.subgraphQueryError} if the query fails.
*/
public async getDripsSetEventsByReceiverUserId(
receiverId: string,
skip: number = 0,
first: number = 100
): Promise<DripsSetEvent[]> {
if (!receiverId) {
throw DripsErrors.argumentError(`Could not get 'DripSet' events: ${nameOf({ receiverId })} is missing.`);
}

type QueryResponse = {
dripsSetEvents: SubgraphTypes.DripsSetEvent[];
};

const response = await this.query<QueryResponse>(gql.getDripsSetEventsByReceiverUserId, {
receiverId,
skip,
first
});

return response?.data?.dripsSetEvents?.map(mapDripsSetEventToDto) || [];
}

/**
* Returns a list of `DripsReceiverSeen` events for the given receiver.
* @param {string} receiverUserId The receiver's user ID.
Expand Down Expand Up @@ -874,7 +905,7 @@ export default class DripsSubgraphClient {

// Filter by asset.
const tokenDripsSetEvents = iterationDripsSetEvents.filter(
(e) => e.assetId == Utils.Asset.getIdFromAddress(tokenAddress)
(e) => e.assetId === Utils.Asset.getIdFromAddress(tokenAddress)
);

dripsSetEvents.push(...tokenDripsSetEvents);
Expand Down
20 changes: 20 additions & 0 deletions src/DripsSubgraph/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,26 @@ query getDripsSetEventsByUserId($userId: String!, $skip: Int, $first: Int) {
}
`;

export const getDripsSetEventsByReceiverUserId = `#graphql
query getDripsSetEventsByUserId($receiverUserId: String!, $skip: Int, $first: Int) {
dripsSetEvents(skip: $skip, first: $first) {
id
userId
assetId
receiversHash
dripsReceiverSeenEvents(where: {receiverUserId: $receiverUserId}){
id
receiverUserId
config
}
dripsHistoryHash
balance
blockTimestamp
maxEnd
}
}
`;

export const getDripsReceiverSeenEventsByReceiverId = `#graphql
query getDripsReceiverSeenEventsByReceiverId($receiverUserId: String!, $skip: Int, $first: Int) {
dripsReceiverSeenEvents(where: {receiverUserId: $receiverUserId}, skip: $skip, first: $first) {
Expand Down
72 changes: 72 additions & 0 deletions src/Metrics/AccountEstimator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* eslint-disable no-dupe-class-members */
import type { SqueezedDripsEvent } from 'src/DripsSubgraph/types';
import Utils from '../utils';
import { DripsErrors } from '../common/DripsError';
import AccountService from './Services/AccountService';
import type { Account, AccountEstimate, UserId } from './types';
import EstimatorService from './Services/EstimatorService';

export default class AccountEstimator {
private readonly _accountService: AccountService;
private readonly _estimatorEngine: EstimatorService;

public readonly userId: string;
public readonly chainId: number;

public account: Account;

private constructor(
userId: UserId,
chainId: number,
account: Account,
accountService: AccountService,
estimatorService: EstimatorService
) {
this.userId = userId;
this.chainId = chainId;
this.account = account;
this._accountService = accountService;
this._estimatorEngine = estimatorService;
}

public static async create(userId: string, chainId: number): Promise<AccountEstimator>;
public static async create(
userId: string,
chainId: number,
accountService?: AccountService,
estimatorService?: EstimatorService
): Promise<AccountEstimator>;
public static async create(
userId: string,
chainId: number,
accountService: AccountService = new AccountService(chainId),
estimatorService: EstimatorService = new EstimatorService()
): Promise<AccountEstimator> {
if (chainId !== accountService.chainId) {
throw new Error(`Chain ID mismatch: ${chainId} !== ${accountService.chainId}`);
}

if (!userId) {
throw DripsErrors.clientInitializationError(`Could not create 'Estimator': user ID is required.`);
}

if (!chainId) {
throw DripsErrors.clientInitializationError(`Could not create 'Estimator': chain ID is required.`);
}

const account = await accountService.fetchAccount(userId);
const estimator = new AccountEstimator(userId, chainId, account, accountService, estimatorService);

return estimator;
}

public async refreshAccount(): Promise<void> {
this.account = await this._accountService.fetchAccount(this.userId);
}

public estimate(excludingSqueezes?: SqueezedDripsEvent[]): AccountEstimate {
const currentCycle = Utils.Cycle.getInfo(this.chainId);

return this._estimatorEngine.estimateAccount(this.account, currentCycle, excludingSqueezes);
}
}
152 changes: 152 additions & 0 deletions src/Metrics/ReceiverMetrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/* eslint-disable no-param-reassign */
/* eslint-disable no-dupe-class-members */

import Utils from '../utils';
import { minMax } from './internals';
import AccountEstimator from './AccountEstimator';
import DripsSetEventService from './Services/DripsSetEventService';
import SplitsSetEventService from './Services/SplitsSetEventService';
import type { ActiveSupporters, TotalSupporters, UserId } from './types';

export default class ReceiverMetrics {
private readonly _dripsSetEventService: DripsSetEventService;
private readonly _splitSetEventService: SplitsSetEventService;

private constructor(
chainId: number,
accountEstimator: AccountEstimator,
dripsSetEventService: DripsSetEventService,
splitsSetEventService: SplitsSetEventService
) {
this._dripsSetEventService = dripsSetEventService;
this._splitSetEventService = splitsSetEventService;
}

public static async create(userId: UserId, chainId: number): Promise<ReceiverMetrics>;
public static async create(
userId: UserId,
chainId: number,
accountEstimator?: AccountEstimator,
dripsSetEventService?: DripsSetEventService,
splitsSetEventService?: SplitsSetEventService
): Promise<ReceiverMetrics>;
public static async create(
userId: UserId,
chainId: number,
accountEstimator?: AccountEstimator,
dripsSetEventService?: DripsSetEventService,
splitsSetEventService?: SplitsSetEventService
): Promise<ReceiverMetrics> {
dripsSetEventService = dripsSetEventService || new DripsSetEventService(chainId);
splitsSetEventService = splitsSetEventService || new SplitsSetEventService(chainId);
accountEstimator = accountEstimator || (await AccountEstimator.create(userId, chainId));

if (
chainId !== accountEstimator.chainId ||
chainId !== dripsSetEventService.chainId ||
chainId !== splitsSetEventService.chainId
) {
throw new Error(`Could not create 'Metrics': all services must be initialized with the same chain ID.`);
}

const metrics = new ReceiverMetrics(chainId, accountEstimator, dripsSetEventService, splitsSetEventService);

return metrics;
}

public async getActiveSupporters(userId: string): Promise<ActiveSupporters> {
if (!userId) {
throw new Error(`Could not get supporters: user ID is required.`);
}

try {
const activeSupporters: ActiveSupporters = { dripping: {}, splitting: [] };

const dripsSetEvents = await this._dripsSetEventService.getAllDripsSetEventsByReceiverUserId(userId);
const dripsSetEventsByUserAndToken = DripsSetEventService.groupByUserAndTokenAddress(dripsSetEvents);

Object.entries(dripsSetEventsByUserAndToken).forEach(([senderId, dripsSetEventsByToken]) => {
Object.entries(dripsSetEventsByToken).forEach(([tokenAddress, tokenDripsSetEvents]) => {
if (tokenDripsSetEvents.length === 0) {
return;
}

const sorted = DripsSetEventService.sortByBlockTimestampASC(tokenDripsSetEvents);
const mostRecentDripsSetEvent = sorted[sorted.length - 1];

const latestReceiverConfig = Utils.DripsReceiverConfiguration.fromUint256(
mostRecentDripsSetEvent.dripsReceiverSeenEvents.filter(
(dripsReceiverSeenEvent) => dripsReceiverSeenEvent.receiverUserId === userId
)[0].config
);

const isActive =
minMax(
'min',
Number(mostRecentDripsSetEvent.maxEnd),
Number(latestReceiverConfig.start) + Number(latestReceiverConfig.duration)
) > BigInt(Date.now());

if (isActive) {
activeSupporters.dripping[senderId][tokenAddress].push(latestReceiverConfig);
}
});
});

const splitsEntries = await this._splitSetEventService.getAllSplitEntriesByReceiverUserId(userId);

splitsEntries.forEach((splitEntry) => {
activeSupporters.splitting.push({ weight: splitEntry.weight });
});

return activeSupporters;
} catch (error: any) {
throw new Error(`Failed to get active supporters for user ${userId}: ${error.message}`);
}
}

public async getTotalSupporters(userId: string): Promise<TotalSupporters> {
if (!userId) {
throw new Error(`Could not get supporters: user ID is required.`);
}

try {
const totalSupporters: TotalSupporters = { dripping: {}, splitting: [] };

const dripsSetEvents = await this._dripsSetEventService.getAllDripsSetEventsByReceiverUserId(userId);
const dripsSetEventsByUserAndToken = DripsSetEventService.groupByTokenAddress(dripsSetEvents);

Object.entries(dripsSetEventsByUserAndToken).forEach(([tokenAddress, tokenDripsSetEvents]) => {
const userIds = new Set<UserId>();

tokenDripsSetEvents.forEach((dripsSetEvent) => {
userIds.add(dripsSetEvent.userId);
});

totalSupporters.dripping[tokenAddress] = userIds;
});

const splitsEntries = await this._splitSetEventService.getAllSplitEntriesByReceiverUserId(userId);

splitsEntries.forEach((splitEntry) => {
totalSupporters.splitting.push({ weight: splitEntry.weight });
});

return totalSupporters;
} catch (error: any) {
throw new Error(`Failed to get total supporters for user ${userId}: ${error.message}`);
}
}

public async getIncomingStreamsValue(): Promise<any> {
throw new Error('Method not implemented.');
}

getTotalValueRaised(): Promise<bigint> {
throw new Error('Method not implemented.');
}

getIncomingSplitsValue(): Promise<bigint> {
throw new Error('Method not implemented.');
}
}
Loading