From e4cafd0b779fc0c3cacfe45c156e59a607be35d5 Mon Sep 17 00:00:00 2001 From: daniel <4954577+jaensen@users.noreply.github.com> Date: Thu, 12 Dec 2024 00:52:55 +0100 Subject: [PATCH] improved the 'getInvitations' method --- packages/data/src/circlesData.ts | 109 ++++++++++------------ packages/data/src/circlesDataInterface.ts | 8 ++ 2 files changed, 56 insertions(+), 61 deletions(-) diff --git a/packages/data/src/circlesData.ts b/packages/data/src/circlesData.ts index 8f359be..e082720 100644 --- a/packages/data/src/circlesData.ts +++ b/packages/data/src/circlesData.ts @@ -1,9 +1,9 @@ -import { CirclesQuery } from './pagedQuery/circlesQuery'; -import { TransactionHistoryRow } from './rows/transactionHistoryRow'; -import { TrustListRow } from './rows/trustListRow'; -import { TokenBalanceRow } from './rows/tokenBalanceRow'; -import { CirclesRpc } from './circlesRpc'; -import { AvatarRow } from './rows/avatarRow'; +import {CirclesQuery} from './pagedQuery/circlesQuery'; +import {TransactionHistoryRow} from './rows/transactionHistoryRow'; +import {TrustListRow} from './rows/trustListRow'; +import {TokenBalanceRow} from './rows/tokenBalanceRow'; +import {CirclesRpc} from './circlesRpc'; +import {AvatarRow} from './rows/avatarRow'; import { attoCirclesToCircles, attoCirclesToStaticAttoCircles, circlesToAttoCircles, @@ -12,19 +12,19 @@ import { tcToCrc, uint8ArrayToCidV0 } from '@circles-sdk/utils'; -import { TrustRelation, TrustRelationRow } from './rows/trustRelationRow'; -import { CirclesDataInterface, GroupQueryParams } from './circlesDataInterface'; -import { Observable } from './observable'; -import { CirclesEvent } from './events/events'; -import { InvitationRow } from './rows/invitationRow'; -import { PagedQueryParams } from './pagedQuery/pagedQueryParams'; -import { Filter } from './rpcSchema/filter'; -import { GroupMembershipRow } from './rows/groupMembershipRow'; -import { GroupRow } from './rows/groupRow'; -import { TokenInfoRow } from './rows/tokenInfoRow'; -import { parseRpcSubscriptionMessage, RcpSubscriptionEvent } from './events/parser'; -import { FilterPredicate } from "./rpcSchema/filterPredicate"; -import { EventRow } from "./pagedQuery/eventRow"; +import {TrustRelation, TrustRelationRow} from './rows/trustRelationRow'; +import {CirclesDataInterface, GroupQueryParams} from './circlesDataInterface'; +import {Observable} from './observable'; +import {CirclesEvent} from './events/events'; +import {InvitationRow} from './rows/invitationRow'; +import {PagedQueryParams} from './pagedQuery/pagedQueryParams'; +import {Filter} from './rpcSchema/filter'; +import {GroupMembershipRow} from './rows/groupMembershipRow'; +import {GroupRow} from './rows/groupRow'; +import {TokenInfoRow} from './rows/tokenInfoRow'; +import {parseRpcSubscriptionMessage, RcpSubscriptionEvent} from './events/parser'; +import {FilterPredicate} from "./rpcSchema/filterPredicate"; +import {EventRow} from "./pagedQuery/eventRow"; export type TrustEvent = { blockNumber: number; @@ -393,7 +393,7 @@ export class CirclesData implements CirclesDataInterface { trustListRows.forEach(row => { const addToBucket = (key: string) => { if (!trustBucket[key]) { - trustBucket[key] = { rows: [], version: new Set() }; + trustBucket[key] = {rows: [], version: new Set()}; } trustBucket[key].rows.push(row); trustBucket[key].version.add(row.version); @@ -410,7 +410,7 @@ export class CirclesData implements CirclesDataInterface { // Determine trust relations return Object.entries(trustBucket) .filter(([avatar]) => avatar !== avatarAddress) - .map(([avatar, { rows, version }]) => { + .map(([avatar, {rows, version}]) => { const versionRelations: { [key: number]: TrustRelation } = {}; const maxTimestamp = Math.max(...rows.map(o => o.timestamp)); @@ -454,7 +454,7 @@ export class CirclesData implements CirclesDataInterface { * @returns The avatar info or undefined if the avatar is not found. */ async getAvatarInfo(avatar: string): Promise { - const avatarInfos = await this.getAvatarInfos([avatar]); + const avatarInfos = await this.getAvatarInfoBatch([avatar]); return avatarInfos.length > 0 ? avatarInfos[0] : undefined; } @@ -463,7 +463,7 @@ export class CirclesData implements CirclesDataInterface { * @param avatars The addresses to check. * @returns An array of avatar info objects. */ - async getAvatarInfos(avatars: string[]): Promise { + async getAvatarInfoBatch(avatars: string[]): Promise { if (avatars.length === 0) { return []; } @@ -604,60 +604,47 @@ export class CirclesData implements CirclesDataInterface { } /** - * TODO: update this comment if implemented - * Gets the invitations sent by an avatar. + * Checks if an avatar has been invited to circles by another avatar. * @param avatar The avatar to get the invitations for. - * @param pageSize The maximum number of invitations per page. - * @returns A CirclesQuery object to fetch the invitations. + * @returns A list of inviters or an empty list if no invitations are found (or the inviter doesn't have enough balance to pay for the invitation fees). */ async getInvitations(avatar: string): Promise { const MIN_TOKENS_REQUIRED = 96; + + // Check if the avatar is still on v1 (else not interesting for invitations) const avatarInfo = await this.getAvatarInfo(avatar); - if (avatarInfo?.version == 2) return []; + if (avatarInfo?.version == 2) { + return []; + } + // Find all avatars trusting the given avatar. + // (mutual trust cannot exist in invitation state - to trust back, the avatar must be on v2 already) const v2Relations = await this.getAggregatedTrustRelations(avatar, 2); + const v2Trusters = v2Relations + .filter(o => o.relation == "trusts") + .map(o => o.subjectAvatar); const humanInviters: AvatarRow[] = []; + const trusterInfoBatch = await this.getAvatarInfoBatch(v2Trusters); - for (const relation of v2Relations) { - const inviterInfo = await this.getAvatarInfo(relation.subjectAvatar); + for (const trusterInfo of trusterInfoBatch) { + // Only humans can inviter other humans + if (!trusterInfo?.isHuman) { + continue; + } - if (inviterInfo?.isHuman && - (relation.relation === 'trusts' || relation.relation === 'mutuallyTrusts')) { - const balance = await this.getTotalBalanceV2(relation.subjectAvatar); - if (parseFloat(balance) >= MIN_TOKENS_REQUIRED) { - humanInviters.push(inviterInfo); - } + // If the inviter doesn't have enough tokens, the user cannot accept their invitation. + // Invitation fees must be paid in the inviter's own token. + const balances = await this.getTokenBalances(trusterInfo.avatar); + const inviterOwnToken = balances.find(o => o.tokenAddress == trusterInfo.avatar); + if (inviterOwnToken && inviterOwnToken.circles >= MIN_TOKENS_REQUIRED) { + // The inviter has enough tokens to pay for the invitation + humanInviters.push(trusterInfo); } } return humanInviters; } - // getInvitations(avatar: string, pageSize: number): CirclesQuery { - // return new CirclesQuery(this.rpc, { - // namespace: 'CrcV2', - // table: 'InviteHuman', - // columns: [ - // 'blockNumber', - // 'transactionIndex', - // 'logIndex', - // 'timestamp', - // 'transactionHash', - // 'inviter', - // 'invited' - // ], - // filter: [ - // { - // Type: 'FilterPredicate', - // FilterType: 'Equals', - // Column: 'inviter', - // Value: avatar.toLowerCase() - // } - // ], - // sortOrder: 'DESC', - // limit: pageSize - // }); - // } /** * Gets the avatar that invited the given avatar. diff --git a/packages/data/src/circlesDataInterface.ts b/packages/data/src/circlesDataInterface.ts index 94cad20..c9f1dc5 100644 --- a/packages/data/src/circlesDataInterface.ts +++ b/packages/data/src/circlesDataInterface.ts @@ -27,6 +27,14 @@ export interface CirclesDataInterface { */ getAvatarInfo(avatar: string): Promise; + /** + * Gets basic information about avatars. + * This includes the signup timestamp, circles version, avatar type and token address/id. + * @param avatar The addresses to check. + * @returns The avatar information or undefined if the address is not an avatar. + */ + getAvatarInfoBatch(avatar: string[]): Promise; + /** * Gets the token info for a given token address. * @param address The address of the token.