Skip to content

Commit f77b690

Browse files
committed
First implementation of the multi layer delegations
1 parent 0d7a9d0 commit f77b690

File tree

2 files changed

+264
-72
lines changed

2 files changed

+264
-72
lines changed

src/flow/flow.controller.ts

Lines changed: 121 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -453,101 +453,151 @@ export class FlowController {
453453
async delegateStatus(@Req() { userId }: AuthedReq) {
454454
const [collectionDelegations, budgetDelegation] = await Promise.all([
455455
this.prismaService.collectionDelegation.findMany({
456-
select: { collectionId: true, metadata: true },
457-
where: { userId, platform: 'FARCASTER' },
456+
select: { collectionId: true, metadata: true, target: true },
457+
where: { userId },
458458
}),
459459
this.prismaService.budgetDelegation.findUnique({
460-
select: { metadata: true },
461-
where: { userId, platform: 'FARCASTER' },
460+
select: { metadata: true, target: true },
461+
where: { userId },
462462
}),
463463
]);
464464

465-
const budgetDelegationMetadata =
466-
budgetDelegation?.metadata as FarcasterMetadata;
465+
const budgetDelegationMetadata = budgetDelegation?.metadata as
466+
| FarcasterMetadata
467+
| object;
467468

468469
let result: any = {
469470
fromYou: {
470-
collections: collectionDelegations.map((el) => {
471-
const metadata = el.metadata as FarcasterMetadata;
472-
return {
473-
...el,
474-
metadata: {
475-
username: metadata.username,
476-
profileUrl: metadata.pfp.url,
477-
},
478-
};
479-
}),
480-
budget: budgetDelegation
481-
? {
482-
...budgetDelegation,
483-
metadata: {
484-
username: budgetDelegationMetadata.username,
485-
profileUrl: budgetDelegationMetadata.pfp.url,
486-
},
487-
}
488-
: null,
471+
collections: collectionDelegations,
472+
budget: budgetDelegation || null,
489473
},
490474
};
491475

492-
const res = await this.prismaService.farcasterConnection.findUnique({
493-
select: { metadata: true },
476+
const farcasterRes =
477+
await this.prismaService.farcasterConnection.findUnique({
478+
select: { metadata: true },
479+
where: { userId },
480+
});
481+
482+
const twitterRes = await this.prismaService.twitterConnection.findUnique({
494483
where: { userId },
495484
});
496485

497-
if (res) {
498-
const fid = (res.metadata as FarcasterMetadata).fid;
499-
const [res2, res3] = await Promise.all([
500-
this.prismaService.collectionDelegation.findMany({
501-
select: { metadata: true, collectionId: true, userId: true },
502-
where: { target: `${fid}` },
503-
}),
504-
this.prismaService.budgetDelegation.findMany({
505-
select: { metadata: true, userId: true },
506-
where: { target: `${fid}` },
507-
}),
508-
]);
486+
const fid = farcasterRes
487+
? (farcasterRes.metadata?.valueOf() as FarcasterMetadata).fid
488+
: null;
489+
490+
const twitterUsername = twitterRes ? twitterRes.username : null;
491+
492+
const uniqueFarcasterCollectionDelegators = fid
493+
? await this.flowService.getCollectionDelegators(`${fid}`, 'FARCASTER')
494+
: [];
495+
496+
const uniqueTwitterCollectionDelegators = twitterUsername
497+
? await this.flowService.getCollectionDelegators(
498+
twitterUsername,
499+
'TWITTER',
500+
)
501+
: [];
502+
503+
const uniqueFarcasterBudgetDelegators = fid
504+
? await this.flowService.getBudgetDelegators(`${fid}`, 'FARCASTER')
505+
: [];
509506

510-
result = {
511-
...result,
512-
toYou: {
513-
uniqueDelegators: new Set([...res2, ...res3].map((el) => el.userId))
514-
.size,
515-
uniqueCollectionDelegators: new Set(res2.map((el) => el.userId)).size,
516-
uniqueBudgetDelegators: new Set(res3.map((el) => el.userId)).size,
517-
collections: res2.map((el) => {
518-
const metadata = el.metadata as FarcasterMetadata;
507+
const uniqueTwitterBudgetDelegators = twitterUsername
508+
? await this.flowService.getBudgetDelegators(twitterUsername, 'TWITTER')
509+
: [];
510+
511+
// const fid = (res.metadata as FarcasterMetadata).fid;
512+
const [res2, res3] = await Promise.all([
513+
this.prismaService.collectionDelegation.findMany({
514+
select: { metadata: true, collectionId: true, userId: true },
515+
where: { target: { in: [`${fid}`, `${twitterUsername}`] } },
516+
}),
517+
this.prismaService.budgetDelegation.findMany({
518+
select: { metadata: true, userId: true },
519+
where: { target: { in: [`${fid}`, `${twitterUsername}`] } },
520+
}),
521+
]);
522+
523+
const uniqueDelegatorsSize = new Set(
524+
[
525+
...uniqueFarcasterBudgetDelegators,
526+
...uniqueFarcasterCollectionDelegators,
527+
...uniqueTwitterBudgetDelegators,
528+
...uniqueTwitterCollectionDelegators,
529+
].map((el) => el.id),
530+
).size;
531+
532+
console.log('res2', res2);
533+
534+
const uniqueBudgetDelegatorsSize = new Set(
535+
[
536+
...uniqueFarcasterBudgetDelegators,
537+
...uniqueTwitterBudgetDelegators,
538+
].map((el) => el.id),
539+
).size;
540+
541+
const uniqueCollectionDelegatorsSize = new Set(
542+
[
543+
...uniqueFarcasterCollectionDelegators,
544+
...uniqueTwitterCollectionDelegators,
545+
].map((el) => el.id),
546+
).size;
547+
548+
result = {
549+
...result,
550+
toYou: {
551+
uniqueDelegators: uniqueDelegatorsSize,
552+
uniqueCollectionDelegators: uniqueCollectionDelegatorsSize,
553+
uniqueBudgetDelegators: uniqueBudgetDelegatorsSize,
554+
collections: await Promise.all(
555+
res2.map(async (el) => {
519556
return {
520557
collectionId: el.collectionId,
521-
metadata: {
522-
username: metadata.username,
523-
profileUrl: metadata.pfp.url,
524-
},
558+
delegators: [
559+
...(!fid
560+
? []
561+
: await this.flowService.getCollectionDelegators(
562+
`${fid}`,
563+
'FARCASTER',
564+
el.collectionId,
565+
)),
566+
...(!twitterUsername
567+
? []
568+
: await this.flowService.getCollectionDelegators(
569+
twitterUsername,
570+
'TWITTER',
571+
el.collectionId,
572+
)),
573+
],
525574
};
526575
}),
527-
budget: res3.map((el) => {
528-
const metadata = el.metadata as FarcasterMetadata;
576+
),
577+
budget: await Promise.all(
578+
res3.map(async () => {
529579
return {
530-
metadata: {
531-
username: metadata.username,
532-
profileUrl: metadata.pfp.url,
533-
},
580+
delegators: [
581+
...(!fid
582+
? []
583+
: await this.flowService.getBudgetDelegators(
584+
`${fid}`,
585+
'FARCASTER',
586+
)),
587+
...(!twitterUsername
588+
? []
589+
: await this.flowService.getBudgetDelegators(
590+
twitterUsername,
591+
'TWITTER',
592+
)),
593+
],
534594
};
535595
}),
536-
},
537-
};
538-
539-
return result;
540-
}
541-
542-
return {
543-
...result,
544-
toYou: {
545-
uniqueCollectionDelegators: 0,
546-
uniqueBudgetDelegators: 0,
547-
collections: [],
548-
budget: [],
596+
),
549597
},
550598
};
599+
600+
return result;
551601
}
552602

553603
@ApiOperation({

src/flow/flow.service.ts

Lines changed: 143 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
FarcasterMetadata,
2626
ProjectRanking,
2727
} from './types';
28-
import { Prisma, ProjectType } from '@prisma/client';
28+
import { DelegationPlatform, Prisma, ProjectType, User } from '@prisma/client';
2929
import axios from 'axios';
3030
import * as fs from 'fs';
3131
import * as FormData from 'form-data';
@@ -534,6 +534,148 @@ export class FlowService {
534534
return makeIt100(ranking.sort((a, b) => a.rank - b.rank));
535535
};
536536

537+
/**
538+
* Find all users who have directly or indirectly delegated to a user with the given social ID.
539+
* @param socialId The Twitter or Farcaster ID of the user receiving delegations
540+
* @returns Array of User objects who have delegated to the target
541+
*/
542+
getCollectionDelegators = async (
543+
socialId: string,
544+
platform?: DelegationPlatform,
545+
collectionId?: number,
546+
): Promise<User[]> => {
547+
// Store users who have delegated to our target
548+
const delegators: User[] = [];
549+
550+
// Track processed userIds to avoid cycles in delegation chains
551+
const processedUserIds = new Set<number>();
552+
553+
// Use BFS to traverse the delegation graph backward
554+
let currentBatch: string[] = [socialId];
555+
556+
while (currentBatch.length > 0) {
557+
// Find all direct delegations to users in the current batch
558+
const directDelegations =
559+
await this.prismaService.collectionDelegation.findMany({
560+
where: {
561+
target: {
562+
in: currentBatch,
563+
mode: 'insensitive',
564+
},
565+
collectionId,
566+
platform,
567+
},
568+
include: {
569+
user: true,
570+
},
571+
});
572+
573+
const nextBatch: string[] = [];
574+
575+
for (const delegation of directDelegations) {
576+
const delegatorId = delegation.userId;
577+
578+
// Avoid processing the same user multiple times
579+
if (!processedUserIds.has(delegatorId)) {
580+
processedUserIds.add(delegatorId);
581+
delegators.push(delegation.user);
582+
583+
// For each delegator, get their social ID to find who delegated to them
584+
}
585+
}
586+
const delegatorSocialIds = await this.convertUserIdsToSocials(
587+
directDelegations.map((el) => el.userId),
588+
);
589+
nextBatch.push(...delegatorSocialIds);
590+
591+
currentBatch = nextBatch;
592+
}
593+
594+
return delegators;
595+
};
596+
597+
/**
598+
* Find all users who have directly or indirectly delegated to a user with the given social ID.
599+
* @param socialId The Twitter or Farcaster ID of the user receiving delegations
600+
* @returns Array of User objects who have delegated to the target
601+
*/
602+
getBudgetDelegators = async (
603+
socialId: string,
604+
platform?: DelegationPlatform,
605+
): Promise<User[]> => {
606+
// Store users who have delegated to our target
607+
const delegators: User[] = [];
608+
609+
// Track processed userIds to avoid cycles in delegation chains
610+
const processedUserIds = new Set<number>();
611+
612+
// Use BFS to traverse the delegation graph backward
613+
let currentBatch: string[] = [socialId];
614+
615+
while (currentBatch.length > 0) {
616+
// Find all direct delegations to users in the current batch
617+
const directDelegations =
618+
await this.prismaService.budgetDelegation.findMany({
619+
where: {
620+
target: {
621+
in: currentBatch,
622+
mode: 'insensitive',
623+
},
624+
platform,
625+
},
626+
include: {
627+
user: true,
628+
},
629+
});
630+
631+
const nextBatch: string[] = [];
632+
633+
for (const delegation of directDelegations) {
634+
const delegatorId = delegation.userId;
635+
636+
// Avoid processing the same user multiple times
637+
if (!processedUserIds.has(delegatorId)) {
638+
processedUserIds.add(delegatorId);
639+
delegators.push(delegation.user);
640+
641+
// For each delegator, get their social ID to find who delegated to them
642+
}
643+
}
644+
const delegatorSocialIds = await this.convertUserIdsToSocials(
645+
directDelegations.map((el) => el.userId),
646+
);
647+
nextBatch.push(...delegatorSocialIds);
648+
649+
currentBatch = nextBatch;
650+
}
651+
652+
return delegators;
653+
};
654+
655+
convertUserIdsToSocials = async (userIds: number[]): Promise<string[]> => {
656+
const socials = new Set<string>();
657+
658+
const [farcasterConnections, twitterConnections] = await Promise.all([
659+
this.prismaService.farcasterConnection.findMany({
660+
where: { userId: { in: userIds } },
661+
}),
662+
this.prismaService.twitterConnection.findMany({
663+
where: { userId: { in: userIds } },
664+
}),
665+
]);
666+
667+
for (const fc of farcasterConnections) {
668+
const metadata = fc.metadata?.valueOf() as FarcasterMetadata;
669+
socials.add(`${metadata.fid}`);
670+
}
671+
672+
for (const tc of twitterConnections) {
673+
socials.add(tc.username);
674+
}
675+
676+
return [...socials];
677+
};
678+
537679
// getRootRanking = async (userId: number) => {
538680
// const [ranking, collectionIds] = await Promise.all([
539681
// this.getRankingFromVotes(userId, null),

0 commit comments

Comments
 (0)