Skip to content
Open
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
5 changes: 2 additions & 3 deletions modules/delegates/api/fetchChainDelegates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function fetchChainDelegates(
const chainId = networkNameToChainId(network);
const data = await gqlRequest({
chainId,
query: allDelegates
query: allDelegates(chainId)
});

return data.delegates.map(d => {
Expand All @@ -30,9 +30,8 @@ export async function fetchChainDelegates(
return {
blockTimestamp,
address: d.ownerAddress,
voteDelegateAddress: d.id,
voteDelegateAddress: d.address,
skyDelegated: formatEther(BigInt(totalDelegated)),
delegations: d.delegations || [], // Include current delegations from subgraph
lastVoteDate: d.voter?.lastVotedTimestamp ? Number(d.voter.lastVotedTimestamp) : null
};
});
Expand Down
4 changes: 2 additions & 2 deletions modules/delegates/api/fetchDelegateAddresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ export async function fetchDelegateAddresses(network: SupportedNetworks): Promis

const data = await gqlRequest({
chainId,
query: allDelegates
query: allDelegates(chainId)
});

const delegates = data.delegates.map(delegate => ({
blockTimestamp: new Date(Number(delegate?.blockTimestamp || 0) * 1000),
delegate: delegate?.ownerAddress,
voteDelegate: delegate?.id
voteDelegate: delegate?.address
})) as AllDelegatesEntry[];

cacheSet(allDelegateAddressesKey, JSON.stringify(delegates), network, ONE_HOUR_IN_MS);
Expand Down
32 changes: 17 additions & 15 deletions modules/delegates/api/fetchDelegatePaginatedDelegations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,35 +28,37 @@ export async function fetchDelegatePaginatedDelegations(
const delegateId = delegateAddress.toLowerCase();
const chainId = networkNameToChainId(network);
const stakingEngineAddresses = [stakingEngineAddressMainnet, stakingEngineAddressTestnet];

const response = await gqlRequest({
chainId,
query: delegateWithPaginatedDelegations,
variables: {
id: delegateId,
first: limit,
skip: offset,
orderBy: 'amount',
orderDirection: 'desc',
excludeAddresses: stakingEngineAddresses,
query: delegateWithPaginatedDelegations(
chainId,
delegateId,
limit,
offset,
'amount',
'desc',
stakingEngineAddresses,
stakingEngineAddresses
}
)
});

if (!response.delegate || !response.delegate.delegations) {
const delegate = response.delegate?.[0];

if (!delegate || !delegate.delegations) {
return {
delegations: [],
total: 0
};
}

const formattedDelegations = formatCurrentDelegations(response.delegate.delegations);
const totalDelegators = response.delegate.delegators || 0;
const hasStakingEngine = response.delegate.stakingEngineDelegations?.length > 0;
const formattedDelegations = formatCurrentDelegations(delegate.delegations);
const totalDelegators = delegate.delegators || 0;
const hasStakingEngine = delegate.stakingEngineDelegations?.length > 0;
const adjustedTotal = hasStakingEngine ? Math.max(0, totalDelegators - 1) : totalDelegators;

return {
delegations: formattedDelegations,
total: adjustedTotal
};
}
}
11 changes: 5 additions & 6 deletions modules/delegates/api/fetchDelegatedTo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,18 @@ export async function fetchDelegatedTo(
const chainId = networkNameToChainId(network);
const delegatesData = await gqlRequest({
chainId,
query: allDelegates
query: allDelegates(chainId)
});
const delegates = delegatesData.delegates;

// Returns the records with the aggregated delegated data
const data = await gqlRequest({
chainId: networkNameToChainId(network),
query: delegatorHistory,
variables: { address: address.toLowerCase() }
chainId,
query: delegatorHistory(chainId, address.toLowerCase())
});
const res: SKYDelegatedToResponse[] = data.delegationHistories.map(x => {
return {
delegateContractAddress: x.delegate.id,
delegateContractAddress: x.delegate.address,
lockAmount: x.amount,
blockTimestamp: new Date(parseInt(x.timestamp) * 1000).toISOString(),
hash: x.txnHash,
Expand All @@ -63,7 +62,7 @@ export async function fetchDelegatedTo(
});
} else {
const delegatingTo = delegates.find(
i => i?.id?.toLowerCase() === delegateContractAddress.toLowerCase()
i => i?.address?.toLowerCase() === delegateContractAddress.toLowerCase()
);

if (!delegatingTo) {
Expand Down
105 changes: 58 additions & 47 deletions modules/delegates/api/fetchDelegates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@
skyDelegated: onChainDelegate.skyDelegated,
proposalsSupported: onChainDelegate.proposalsSupported,
execSupported: undefined,
delegations: onChainDelegate.delegations, // Include current delegations from subgraph
blockTimestamp: onChainDelegate.blockTimestamp
};
}
Expand Down Expand Up @@ -172,13 +171,18 @@
return delegatesInfo;
}

// Helper to build Hasura where condition strings for delegate filters
function buildDelegateWhereConditions(baseConditions: string[], additionalConditions: string[]): string[] {
return [...baseConditions, ...additionalConditions];
}

export async function fetchDelegatesPaginated({
network,
pageSize,
page,
orderBy,
orderDirection,
seed,

Check warning on line 185 in modules/delegates/api/fetchDelegates.ts

View workflow job for this annotation

GitHub Actions / unit

'seed' is defined but never used
delegateType,
searchTerm
}: DelegatesValidatedQueryParams): Promise<DelegatesPaginatedAPIResponse> {
Expand All @@ -195,60 +199,70 @@
const { alignedDelegatesCount, shadowDelegatesCount, totalDelegatesCount } =
getDelegatesCounts(filteredDelegateEntries);

// If there are no aligned delegates, the id_not_in filter will filter out everything if we give it an empty array
// If there are no aligned delegates, the _nin filter will filter out everything if we give it an empty array
const alignedDelegatesAddressesForNotInQuery =
alignedDelegatesAddresses.length > 0
? alignedDelegatesAddresses
: ['0x0000000000000000000000000000000000000000'];

const baseDelegatesQueryFilter: any = { and: [{ version: '3' }] };
// Build Hasura where conditions
// Note: Envio entity IDs are prefixed with chainId (e.g. "1-0xabc..."), so we must prefix addresses when filtering by id
// We use _ilike/_nilike for case-insensitive address matching since Envio stores addresses with inconsistent casing
const ilikeId = (a: string) => `{ id: { _ilike: "${chainId}-${a}" } }`;
const nilikeId = (a: string) => `{ id: { _nilike: "${chainId}-${a}" } }`;
const baseConditions: string[] = ['{ version: { _eq: "3" } }'];
if (searchTerm) {
baseDelegatesQueryFilter.and.push({ id_in: filteredDelegateAddresses });
const addrs = filteredDelegateAddresses.map(ilikeId).join(', ');
baseConditions.push(`{ _or: [${addrs}] }`);
if (delegateType === DelegateTypeEnum.ALIGNED) {
baseDelegatesQueryFilter.and.push({ id_in: alignedDelegatesAddresses });
baseDelegatesQueryFilter.and.push({ id_not_in: alignedDelegatesAddressesForNotInQuery });
const aligned = alignedDelegatesAddresses.map(ilikeId).join(', ');
baseConditions.push(`{ _or: [${aligned}] }`);
const notIn = alignedDelegatesAddressesForNotInQuery.map(nilikeId).join(', ');
baseConditions.push(`{ _and: [${notIn}] }`);
}
} else if (delegateType === DelegateTypeEnum.ALIGNED) {
baseDelegatesQueryFilter.and.push({ id_in: alignedDelegatesAddresses });
const aligned = alignedDelegatesAddresses.map(ilikeId).join(', ');
baseConditions.push(`{ _or: [${aligned}] }`);
} else if (delegateType === DelegateTypeEnum.SHADOW) {
baseDelegatesQueryFilter.and.push({ id_not_in: alignedDelegatesAddressesForNotInQuery });
const notIn = alignedDelegatesAddressesForNotInQuery.map(nilikeId).join(', ');
baseConditions.push(`{ _and: [${notIn}] }`);
}

//get all aligned delegates (that match the filter)for the first page
const alignedFilterFirstPage = {
and: [...(baseDelegatesQueryFilter.and || []), { id_in: alignedDelegatesAddresses }]
};
//use this for subsequent pages as well as part of the first page
const shadowFilterFirstPage = {
and: [...(baseDelegatesQueryFilter.and || []), { id_not_in: alignedDelegatesAddressesForNotInQuery }]
};
const alignedCondition = `{ _or: [${alignedDelegatesAddresses.map(ilikeId).join(', ')}] }`;
const shadowCondition = `{ _and: [${alignedDelegatesAddressesForNotInQuery
.map(nilikeId)
.join(', ')}] }`;

const queryOrderBy = orderBy === DelegateOrderByEnum.RANDOM ? DelegateOrderByEnum.SKY : orderBy;
const alignedWhereConditions = buildDelegateWhereConditions(baseConditions, [alignedCondition]);
const shadowWhereConditions = buildDelegateWhereConditions(baseConditions, [shadowCondition]);

const delegatesQueryFirstPageVariables = {
first: pageSize,
orderBy: queryOrderBy,
orderDirection,
alignedFilter: alignedFilterFirstPage,
shadowFilter: shadowFilterFirstPage,
alignedDelegates: alignedDelegatesAddresses
};
const queryOrderBy = orderBy === DelegateOrderByEnum.RANDOM ? DelegateOrderByEnum.SKY : orderBy;

const delegatesQuerySubsequentPagesVariables = {
first: pageSize,
skip: (page - 1) * pageSize,
orderBy: queryOrderBy,
orderDirection,
filter: shadowFilterFirstPage
};
const query =
page === 1
? delegatesQueryFirstPage({
chainId,
limit: pageSize,
orderBy: queryOrderBy,
orderDirection,
shadowWhereConditions,
alignedWhereConditions
})
: delegatesQuerySubsequentPages({
chainId,
limit: pageSize,
offset: (page - 1) * pageSize,
orderBy: queryOrderBy,
orderDirection,
whereConditions: shadowWhereConditions
});

const [githubExecutives, delegatesExecSupport, delegatesQueryRes, delegationMetrics] = await Promise.all([
getGithubExecutives(network),
fetchDelegatesExecSupport(network),
gqlRequest<any>({
chainId,
query: page === 1 ? delegatesQueryFirstPage : delegatesQuerySubsequentPages,
variables: page === 1 ? delegatesQueryFirstPageVariables : delegatesQuerySubsequentPagesVariables
query
}),
fetchDelegationMetrics(network)
]);
Expand All @@ -264,14 +278,15 @@
delegate.ownerAddress.toLowerCase()
);

const gaslessChainId = networkNameToChainId(getGaslessNetwork(network));
const lastVotedArbitrumArray = await gqlRequest<any>({
chainId: networkNameToChainId(getGaslessNetwork(network)),
query: lastVotedArbitrum,
variables: { argAddresses: combinedDelegateOwnerAddresses }
chainId: gaslessChainId,
query: lastVotedArbitrum(gaslessChainId, combinedDelegateOwnerAddresses)
});

const lastVotedArbitrumObj = lastVotedArbitrumArray.arbitrumVoters.reduce((acc, voter) => {
acc[voter.id] = voter.pollVotes && voter.pollVotes.length > 0 ? Number(voter.pollVotes[0].blockTime) : 0;
const address = voter.address;
acc[address] = voter.pollVotes && voter.pollVotes.length > 0 ? Number(voter.pollVotes[0].blockTime) : 0;
return acc;
}, {});

Expand All @@ -295,12 +310,13 @@
totalDelegators: delegationMetrics.delegatorCount
},
delegates: combinedDelegates.map(delegate => {
const allDelegatesEntry = allDelegatesWithNamesAndLinks.find(del => del.voteDelegate === delegate.id);
const delegateId = delegate.address;
const allDelegatesEntry = allDelegatesWithNamesAndLinks.find(del => del.voteDelegate === delegateId);

const githubDelegate = githubDelegates?.find(ghDelegate => ghDelegate.name === allDelegatesEntry?.name);

const votedProposals = delegatesExecSupport.data?.find(
del => del.voteDelegate === delegate.id
del => del.voteDelegate === delegateId
)?.votedProposals;
const execSupported = githubExecutives.find(proposal =>
votedProposals?.find(vp => vp.toLowerCase() === proposal?.address?.toLowerCase())
Expand Down Expand Up @@ -335,21 +351,16 @@

const lastVoteTimestamp = Math.max(lastVoteMainnet, lastVoteArbitrum);

const totalDelegated: bigint = delegate.delegations.reduce(
(acc, curr) => acc + BigInt(curr?.amount || 0n),
0n
);

return {
name: githubDelegate?.name || 'Shadow Delegate',
voteDelegateAddress: delegate.id,
voteDelegateAddress: delegateId,
address: delegate.ownerAddress,
status: githubDelegate ? DelegateStatusEnum.aligned : DelegateStatusEnum.shadow,
creationDate: finalCreationDate,
picture: githubDelegate?.picture,
communication: githubDelegate?.communication,
combinedParticipation: githubDelegate?.combinedParticipation,
skyDelegated: formatEther(totalDelegated),
skyDelegated: formatEther(BigInt(delegate.totalDelegated || '0')),
delegatorCount: delegate.delegators,
lastVoteDate: lastVoteTimestamp > 0 ? new Date(lastVoteTimestamp * 1000) : null,
proposalsSupported: votedProposals?.length || 0,
Expand Down
7 changes: 4 additions & 3 deletions modules/delegates/api/fetchDelegatesExecSupport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { networkNameToChainId } from 'modules/web3/helpers/chain';
import logger from 'lib/logger';
import { DelegateExecSupport } from '../types';
import { TEN_MINUTES_IN_MS } from 'modules/app/constants/time';
import { stripChainIdPrefix } from 'modules/gql/gqlUtils';

export async function fetchDelegatesExecSupport(network: SupportedNetworks): Promise<{
error: boolean;
Expand All @@ -25,12 +26,12 @@ export async function fetchDelegatesExecSupport(network: SupportedNetworks): Pro

const data = await gqlRequest({
chainId,
query: allDelegatesExecSupport
query: allDelegatesExecSupport(chainId)
});

const delegatesExecSupport: DelegateExecSupport[] = data.delegates.map(delegate => ({
voteDelegate: delegate.id,
votedProposals: delegate.voter.currentSpellsV2.map(spell => spell.id)
voteDelegate: delegate.address,
votedProposals: (delegate.voter?.currentSpellsV2 || []).map(stripChainIdPrefix)
}));

cacheSet(allDelegatesExecSupportKey, JSON.stringify(delegatesExecSupport), network, TEN_MINUTES_IN_MS);
Expand Down
11 changes: 4 additions & 7 deletions modules/delegates/api/fetchDelegationEventsByAddresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,16 @@ export async function fetchDelegationEventsByAddresses(
const engine =
network === SupportedNetworks.TENDERLY ? stakingEngineAddressTestnet : stakingEngineAddressMainnet;
try {
const chainId = networkNameToChainId(network);
const data = await gqlRequest({
chainId: networkNameToChainId(network),
query: delegateHistoryArray,
variables: {
delegates: addresses,
engines: [engine.toLowerCase()]
}
chainId,
query: delegateHistoryArray(chainId, addresses, [engine.toLowerCase()])
});
const flattenedData = data.delegates.flatMap(delegate => delegate.delegationHistory);

const addressData: SkyLockedDelegateApiResponse[] = flattenedData.map(x => {
return {
delegateContractAddress: x.delegate.id,
delegateContractAddress: x.delegate.address,
immediateCaller: x.delegator,
lockAmount: formatEther(x.amount),
blockNumber: x.blockNumber,
Expand Down
23 changes: 11 additions & 12 deletions modules/delegates/api/fetchDelegationEventsByUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,23 @@ export async function fetchDelegationEventsByUser(
network: SupportedNetworks
): Promise<SkyLockedDelegateApiResponse[]> {
try {
const chainId = networkNameToChainId(network);
const data = await gqlRequest({
chainId: networkNameToChainId(network),
query: userDelegationToDelegate,
variables: {
delegate: delegateAddress.toLowerCase(),
delegator: userAddress.toLowerCase()
}
chainId,
query: userDelegationToDelegate(chainId, delegateAddress.toLowerCase(), userAddress.toLowerCase())
});

if (!data.delegate) {

const delegate = data.delegate?.[0];

if (!delegate) {
return [];
}
const delegationHistory = data.delegate.delegationHistory;

const delegationHistory = delegate.delegationHistory;

const addressData: SkyLockedDelegateApiResponse[] = delegationHistory.map(x => {
return {
delegateContractAddress: x.delegate.id,
delegateContractAddress: x.delegate.address,
immediateCaller: x.delegator,
lockAmount: formatEther(x.amount),
blockNumber: x.blockNumber,
Expand All @@ -52,4 +51,4 @@ export async function fetchDelegationEventsByUser(
logger.error('fetchDelegationEventsByUser: Error fetching delegation events', e.message);
return [];
}
}
}
Loading
Loading