From 49f64bcc047979f77870725bbe7cfb193ebf4a9f Mon Sep 17 00:00:00 2001 From: daniel <4954577+jaensen@users.noreply.github.com> Date: Wed, 27 Nov 2024 12:14:10 +0100 Subject: [PATCH 1/4] allow to filter for version in getAggregatedTrustRelations() and use this to filter only relations applicable to the current version of the avatar, --- package.json | 2 +- packages/abi-v1/package.json | 2 +- packages/abi-v2/package.json | 2 +- packages/adapter-cometh/package.json | 4 +- packages/adapter-ethers/package.json | 6 +- packages/adapter-safe-app/package.json | 4 +- packages/adapter-safe/package.json | 2 +- packages/adapter/package.json | 2 +- packages/data/package.json | 4 +- packages/data/src/circlesData.ts | 80 ++++++++++++++++------ packages/data/src/circlesDataInterface.ts | 3 +- packages/data/src/rows/trustRelationRow.ts | 20 +++++- packages/profiles/package.json | 4 +- packages/sdk/package.json | 12 ++-- packages/sdk/src/v1/v1Avatar.ts | 2 +- packages/sdk/src/v2/v2Avatar.ts | 2 +- packages/utils/package.json | 2 +- 17 files changed, 103 insertions(+), 50 deletions(-) diff --git a/package.json b/package.json index 0042b10..3107765 100644 --- a/package.json +++ b/package.json @@ -36,5 +36,5 @@ }, "name": "@cirlces-sdk/root", "license": "MIT", - "version": "0.14.0" + "version": "0.14.1" } diff --git a/packages/abi-v1/package.json b/packages/abi-v1/package.json index 6084b9b..5158488 100644 --- a/packages/abi-v1/package.json +++ b/packages/abi-v1/package.json @@ -1,6 +1,6 @@ { "name": "@circles-sdk/abi-v1", - "version": "0.14.0", + "version": "0.14.1", "description": "", "type": "module", "main": "./dist/index.js", diff --git a/packages/abi-v2/package.json b/packages/abi-v2/package.json index 40db207..fa0b4e7 100644 --- a/packages/abi-v2/package.json +++ b/packages/abi-v2/package.json @@ -1,6 +1,6 @@ { "name": "@circles-sdk/abi-v2", - "version": "0.14.0", + "version": "0.14.1", "description": "", "type": "module", "main": "./dist/index.js", diff --git a/packages/adapter-cometh/package.json b/packages/adapter-cometh/package.json index f2de349..4a15c29 100644 --- a/packages/adapter-cometh/package.json +++ b/packages/adapter-cometh/package.json @@ -1,6 +1,6 @@ { "name": "@circles-sdk/adapter-cometh", - "version": "0.14.0", + "version": "0.14.1", "description": "", "type": "module", "main": "./dist/index.js", @@ -17,7 +17,7 @@ "build": "rollup -c" }, "dependencies": { - "@circles-sdk/adapter": "0.14.0", + "@circles-sdk/adapter": "0.14.1", "@cometh/connect-sdk": "1.2.29" }, "keywords": [], diff --git a/packages/adapter-ethers/package.json b/packages/adapter-ethers/package.json index 115460b..682ed39 100644 --- a/packages/adapter-ethers/package.json +++ b/packages/adapter-ethers/package.json @@ -1,6 +1,6 @@ { "name": "@circles-sdk/adapter-ethers", - "version": "0.14.0", + "version": "0.14.1", "description": "", "type": "module", "main": "./dist/index.js", @@ -18,8 +18,8 @@ }, "dependencies": { "ethers": "^6.13.2", - "@circles-sdk/adapter": "0.14.0", - "@circles-sdk/utils": "0.14.0" + "@circles-sdk/adapter": "0.14.1", + "@circles-sdk/utils": "0.14.1" }, "keywords": [], "author": "", diff --git a/packages/adapter-safe-app/package.json b/packages/adapter-safe-app/package.json index 514bdc2..81cf7c8 100644 --- a/packages/adapter-safe-app/package.json +++ b/packages/adapter-safe-app/package.json @@ -1,6 +1,6 @@ { "name": "@circles-sdk/adapter-safe-app", - "version": "0.14.0", + "version": "0.14.1", "description": "", "type": "module", "main": "./dist/index.js", @@ -17,7 +17,7 @@ "build": "rollup -c" }, "dependencies": { - "@circles-sdk/adapter": "0.14.0", + "@circles-sdk/adapter": "0.14.1", "@safe-global/safe-apps-sdk": "^9.1.0" }, "keywords": [], diff --git a/packages/adapter-safe/package.json b/packages/adapter-safe/package.json index 1447823..2c44320 100644 --- a/packages/adapter-safe/package.json +++ b/packages/adapter-safe/package.json @@ -1,6 +1,6 @@ { "name": "@circles-sdk/adapter-safe", - "version": "0.14.0", + "version": "0.14.1", "description": "", "type": "module", "main": "./dist/index.js", diff --git a/packages/adapter/package.json b/packages/adapter/package.json index 3406968..079811c 100644 --- a/packages/adapter/package.json +++ b/packages/adapter/package.json @@ -1,6 +1,6 @@ { "name": "@circles-sdk/adapter", - "version": "0.14.0", + "version": "0.14.1", "description": "", "type": "module", "main": "./dist/index.js", diff --git a/packages/data/package.json b/packages/data/package.json index e384767..e608ce1 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -1,6 +1,6 @@ { "name": "@circles-sdk/data", - "version": "0.14.0", + "version": "0.14.1", "description": "", "type": "module", "main": "./dist/index.js", @@ -17,7 +17,7 @@ "build": "rollup -c" }, "dependencies": { - "@circles-sdk/utils": "0.14.0" + "@circles-sdk/utils": "0.14.1" }, "keywords": [], "author": "", diff --git a/packages/data/src/circlesData.ts b/packages/data/src/circlesData.ts index aed9408..7ae4579 100644 --- a/packages/data/src/circlesData.ts +++ b/packages/data/src/circlesData.ts @@ -356,11 +356,24 @@ export class CirclesData implements CirclesDataInterface { /** * Gets all trust relations of an avatar and groups mutual trust relations together. * @param avatarAddress The address to get the trust relations for. + * @param version The version of the trust relations to get (default: undefined - queries both). */ - async getAggregatedTrustRelations(avatarAddress: string): Promise { + /** + * Retrieves and aggregates trust relations for a given avatar. + * + * - Fetches all trust relations involving the avatar. + * - Groups trust relations based on the counterpart (truster/trustee). + * - Determines the type of relationship: mutual trust, trusts, or trusted by. + * - Handles cases where relationships differ across versions and includes detailed metadata. + * + * @param avatarAddress The address of the avatar to retrieve trust relations for. + * @param version Optional version filter (defaults to retrieving all versions). + * @returns Aggregated trust relations, including relation type, versions, and timestamp. + */ + async getAggregatedTrustRelations(avatarAddress: string, version?: number): Promise { const pageSize = 1000; const trustsQuery = this.getTrustRelations(avatarAddress, pageSize); - const trustListRows: TrustListRow[] = []; + let trustListRows: TrustListRow[] = []; // Fetch all trust relations while (await trustsQuery.queryNextPage()) { @@ -370,41 +383,66 @@ export class CirclesData implements CirclesDataInterface { if (resultRows.length < pageSize) break; } + // Filter by version if provided + if (version !== undefined) { + trustListRows = trustListRows.filter(row => row.version === version); + } + // Group trust list rows by truster and trustee - const trustBucket: { [avatar: string]: TrustListRow[] } = {}; + const trustBucket: { [avatar: string]: { rows: TrustListRow[]; version: Set } } = {}; trustListRows.forEach(row => { + const addToBucket = (key: string) => { + if (!trustBucket[key]) { + trustBucket[key] = {rows: [], version: new Set()}; + } + trustBucket[key].rows.push(row); + trustBucket[key].version.add(row.version); + }; + if (row.truster !== avatarAddress) { - trustBucket[row.truster] = trustBucket[row.truster] || []; - trustBucket[row.truster].push(row); + addToBucket(row.truster); } if (row.trustee !== avatarAddress) { - trustBucket[row.trustee] = trustBucket[row.trustee] || []; - trustBucket[row.trustee].push(row); + addToBucket(row.trustee); } }); // Determine trust relations return Object.entries(trustBucket) .filter(([avatar]) => avatar !== avatarAddress) - .map(([avatar, rows]) => { + .map(([avatar, {rows, version}]) => { + const versionRelations: { [key: number]: TrustRelation } = {}; const maxTimestamp = Math.max(...rows.map(o => o.timestamp)); - let relation: TrustRelation; - - if (rows.length === 2) { - relation = 'mutuallyTrusts'; - } else if (rows[0].trustee === avatarAddress) { - relation = 'trustedBy'; - } else if (rows[0].truster === avatarAddress) { - relation = 'trusts'; - } else { - throw new Error(`Unexpected trust list row. Couldn't determine trust relation.`); - } + + // Process each version separately + Array.from(version).forEach(ver => { + const versionRows = rows.filter(row => row.version === ver); + + if (versionRows.length === 2) { + versionRelations[ver] = 'mutuallyTrusts'; + } else if (versionRows[0]?.trustee === avatarAddress) { + versionRelations[ver] = 'trustedBy'; + } else if (versionRows[0]?.truster === avatarAddress) { + versionRelations[ver] = 'trusts'; + } else { + throw new Error(`Unexpected trust list row for version ${ver}. Couldn't determine trust relation.`); + } + }); + + // Combine relations for all versions + const distinctRelations = Array.from(new Set(Object.values(versionRelations))); + + // If relations differ between versions, mark as "variesByVersion" + const combinedRelation = + distinctRelations.length === 1 ? distinctRelations[0] : 'variesByVersion'; return { subjectAvatar: avatarAddress, - relation: relation, + relation: combinedRelation, objectAvatar: avatar, - timestamp: maxTimestamp + timestamp: maxTimestamp, + versions: Array.from(version), + versionSpecificRelations: versionRelations }; }); } diff --git a/packages/data/src/circlesDataInterface.ts b/packages/data/src/circlesDataInterface.ts index 016c525..cd4705b 100644 --- a/packages/data/src/circlesDataInterface.ts +++ b/packages/data/src/circlesDataInterface.ts @@ -74,8 +74,9 @@ export interface CirclesDataInterface { /** * Gets all trust relations of an avatar and groups mutual trust relations together. * @param avatar The address to get the trust relations for. + * @param version The version of the trust relations to get (default: undefined - queries both). */ - getAggregatedTrustRelations(avatar: string): Promise; + getAggregatedTrustRelations(avatar: string, version?: number): Promise; /** * Subscribes to Circles events. diff --git a/packages/data/src/rows/trustRelationRow.ts b/packages/data/src/rows/trustRelationRow.ts index 4a96a28..b96c0a1 100644 --- a/packages/data/src/rows/trustRelationRow.ts +++ b/packages/data/src/rows/trustRelationRow.ts @@ -5,27 +5,41 @@ export type TrustRelation = 'trusts' | 'trustedBy' | 'mutuallyTrusts' - | 'selfTrusts'; + | 'selfTrusts' + | 'variesByVersion'; /** - * A single avatar to avatar trust relation that can be either one-way or mutual. + * A single avatar-to-avatar trust relation that can be either one-way, mutual, or version-specific. */ export interface TrustRelationRow { /** * The avatar. */ subjectAvatar: string; + /** * The trust relation. + * Can be one of the defined TrustRelation values or "variesByVersion" for mixed states across versions. */ relation: TrustRelation; + /** * Who's trusted by or is trusting the avatar. */ objectAvatar: string; /** - * When the last trust relation (in either direction) was last established. + * When the last trust relation (in either direction) was established. */ timestamp: number; + + /** + * The versions involved in this trust relation. + */ + versions: number[]; + + /** + * A map of version-specific trust relations, providing granular details per version. + */ + versionSpecificRelations?: { [version: number]: TrustRelation }; } \ No newline at end of file diff --git a/packages/profiles/package.json b/packages/profiles/package.json index 1881f45..75244f2 100644 --- a/packages/profiles/package.json +++ b/packages/profiles/package.json @@ -1,6 +1,6 @@ { "name": "@circles-sdk/profiles", - "version": "0.14.0", + "version": "0.14.1", "description": "", "type": "module", "main": "./dist/index.js", @@ -17,7 +17,7 @@ "build": "rollup -c" }, "dependencies": { - "@circles-sdk/utils": "0.14.0" + "@circles-sdk/utils": "0.14.1" }, "keywords": [], "author": "", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index c6fb4b4..0a0a6cf 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@circles-sdk/sdk", - "version": "0.14.0", + "version": "0.14.1", "description": "", "type": "module", "main": "./dist/index.js", @@ -17,11 +17,11 @@ "author": "", "license": "MIT", "dependencies": { - "@circles-sdk/abi-v1": "0.14.0", - "@circles-sdk/abi-v2": "0.14.0", - "@circles-sdk/data": "0.14.0", - "@circles-sdk/profiles": "0.14.0", - "@circles-sdk/adapter-ethers": "0.14.0", + "@circles-sdk/abi-v1": "0.14.1", + "@circles-sdk/abi-v2": "0.14.1", + "@circles-sdk/data": "0.14.1", + "@circles-sdk/profiles": "0.14.1", + "@circles-sdk/adapter-ethers": "0.14.1", "ethers": "^6.13.2", "multihashes": "^4.0.3" }, diff --git a/packages/sdk/src/v1/v1Avatar.ts b/packages/sdk/src/v1/v1Avatar.ts index 2eb9a76..363f0e8 100644 --- a/packages/sdk/src/v1/v1Avatar.ts +++ b/packages/sdk/src/v1/v1Avatar.ts @@ -250,7 +250,7 @@ export class V1Avatar implements AvatarInterface { } async getTrustRelations(): Promise { - return this.sdk.data.getAggregatedTrustRelations(this.address); + return this.sdk.data.getAggregatedTrustRelations(this.address, 1); } async getTransactionHistory(pageSize: number): Promise> { diff --git a/packages/sdk/src/v2/v2Avatar.ts b/packages/sdk/src/v2/v2Avatar.ts index c599963..fa457e4 100644 --- a/packages/sdk/src/v2/v2Avatar.ts +++ b/packages/sdk/src/v2/v2Avatar.ts @@ -117,7 +117,7 @@ export class V2Avatar implements AvatarInterfaceV2 { } async getTrustRelations(): Promise { - return this.sdk.data.getAggregatedTrustRelations(this.address); + return this.sdk.data.getAggregatedTrustRelations(this.address, 2); } async getBalances(): Promise { diff --git a/packages/utils/package.json b/packages/utils/package.json index 829a4f1..59d6190 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@circles-sdk/utils", - "version": "0.14.0", + "version": "0.14.1", "description": "", "type": "module", "main": "./dist/index.js", From 23823837758693df43da245843150b508124712d Mon Sep 17 00:00:00 2001 From: Wagalidoom Date: Tue, 3 Dec 2024 19:15:26 +0100 Subject: [PATCH 2/4] feat: add get inviters function --- packages/sdk/src/AvatarInterface.ts | 5 +++++ packages/sdk/src/v1/v1Avatar.ts | 27 ++++++++++++++++++++++----- packages/sdk/src/v2/v2Avatar.ts | 5 +++++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/packages/sdk/src/AvatarInterface.ts b/packages/sdk/src/AvatarInterface.ts index cfed068..f96b70f 100644 --- a/packages/sdk/src/AvatarInterface.ts +++ b/packages/sdk/src/AvatarInterface.ts @@ -117,6 +117,11 @@ export interface AvatarInterface { * Gets the total supply of either this avatar's Personal or Group Circles. */ getTotalSupply(): Promise; + + /** + * Gets all inviters of the avatar (for v1). + */ + getInviters(): Promise; } /** diff --git a/packages/sdk/src/v1/v1Avatar.ts b/packages/sdk/src/v1/v1Avatar.ts index 363f0e8..39938fc 100644 --- a/packages/sdk/src/v1/v1Avatar.ts +++ b/packages/sdk/src/v1/v1Avatar.ts @@ -2,9 +2,9 @@ import { ContractRunner, ContractTransactionReceipt, ethers, TransactionReceipt } from 'ethers'; -import {Sdk} from '../sdk'; -import {AvatarInterface} from '../AvatarInterface'; -import {Token, Token__factory} from '@circles-sdk/abi-v1'; +import { Sdk } from '../sdk'; +import { AvatarInterface } from '../AvatarInterface'; +import { Token, Token__factory } from '@circles-sdk/abi-v1'; import { AvatarRow, CirclesQuery, @@ -12,8 +12,8 @@ import { TransactionHistoryRow, TrustRelationRow } from '@circles-sdk/data'; -import {crcToTc} from '@circles-sdk/utils'; -import {TransactionResponse} from "@circles-sdk/adapter"; +import { crcToTc } from '@circles-sdk/utils'; +import { TransactionResponse } from "@circles-sdk/adapter"; export class V1Avatar implements AvatarInterface { public readonly sdk: Sdk; @@ -292,4 +292,21 @@ export class V1Avatar implements AvatarInterface { throw new Error('Pathfinder is not available'); } } + + async getInviters(): Promise { + const v2Relations = await this.sdk.data.getAggregatedTrustRelations(this.address, 2); + + const humanInviters: TrustRelationRow[] = []; + + for (const relation of v2Relations) { + const inviterInfo = await this.sdk.data.getAvatarInfo(relation.subjectAvatar); + + if (inviterInfo?.isHuman && + (relation.relation === 'trusts' || relation.relation === 'mutuallyTrusts')) { + humanInviters.push(relation); + } + } + + return humanInviters; + } } \ No newline at end of file diff --git a/packages/sdk/src/v2/v2Avatar.ts b/packages/sdk/src/v2/v2Avatar.ts index fa457e4..222a526 100644 --- a/packages/sdk/src/v2/v2Avatar.ts +++ b/packages/sdk/src/v2/v2Avatar.ts @@ -434,4 +434,9 @@ export class V2Avatar implements AvatarInterfaceV2 { throw new Error('Name registry is not available'); } } + + // We don't return any inviter if avatar is already in v2 + async getInviters(): Promise { + return []; + } } \ No newline at end of file From 2ccd20f4fef5b11e513bac37eddd4959b0c7f1ea Mon Sep 17 00:00:00 2001 From: Wagalidoom Date: Wed, 4 Dec 2024 11:08:12 +0100 Subject: [PATCH 3/4] feat: move getInvitations into circles data --- packages/data/src/circlesData.ts | 112 +++++++++++++--------- packages/data/src/circlesDataInterface.ts | 3 +- packages/sdk/src/AvatarInterface.ts | 5 - packages/sdk/src/v1/v1Avatar.ts | 17 ---- packages/sdk/src/v2/v2Avatar.ts | 5 - 5 files changed, 70 insertions(+), 72 deletions(-) diff --git a/packages/data/src/circlesData.ts b/packages/data/src/circlesData.ts index 7ae4579..8f359be 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)); @@ -604,36 +604,60 @@ export class CirclesData implements CirclesDataInterface { } /** + * TODO: update this comment if implemented * Gets the invitations sent by an 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. */ - 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() + async getInvitations(avatar: string): Promise { + const MIN_TOKENS_REQUIRED = 96; + const avatarInfo = await this.getAvatarInfo(avatar); + if (avatarInfo?.version == 2) return []; + + const v2Relations = await this.getAggregatedTrustRelations(avatar, 2); + + const humanInviters: AvatarRow[] = []; + + for (const relation of v2Relations) { + const inviterInfo = await this.getAvatarInfo(relation.subjectAvatar); + + 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); } - ], - sortOrder: 'DESC', - limit: pageSize - }); + } + } + + 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 cd4705b..94cad20 100644 --- a/packages/data/src/circlesDataInterface.ts +++ b/packages/data/src/circlesDataInterface.ts @@ -89,7 +89,8 @@ export interface CirclesDataInterface { * @param avatar The address to get the invitations for. * @param pageSize The maximum number of invitations per page. */ - getInvitations(avatar: string, pageSize: number): CirclesQuery; + // getInvitations(avatar: string, pageSize: number): CirclesQuery; + getInvitations(avatar: string, pageSize: number): Promise; /** * Gets the avatar that invited the given avatar. diff --git a/packages/sdk/src/AvatarInterface.ts b/packages/sdk/src/AvatarInterface.ts index f96b70f..cfed068 100644 --- a/packages/sdk/src/AvatarInterface.ts +++ b/packages/sdk/src/AvatarInterface.ts @@ -117,11 +117,6 @@ export interface AvatarInterface { * Gets the total supply of either this avatar's Personal or Group Circles. */ getTotalSupply(): Promise; - - /** - * Gets all inviters of the avatar (for v1). - */ - getInviters(): Promise; } /** diff --git a/packages/sdk/src/v1/v1Avatar.ts b/packages/sdk/src/v1/v1Avatar.ts index 39938fc..1359971 100644 --- a/packages/sdk/src/v1/v1Avatar.ts +++ b/packages/sdk/src/v1/v1Avatar.ts @@ -292,21 +292,4 @@ export class V1Avatar implements AvatarInterface { throw new Error('Pathfinder is not available'); } } - - async getInviters(): Promise { - const v2Relations = await this.sdk.data.getAggregatedTrustRelations(this.address, 2); - - const humanInviters: TrustRelationRow[] = []; - - for (const relation of v2Relations) { - const inviterInfo = await this.sdk.data.getAvatarInfo(relation.subjectAvatar); - - if (inviterInfo?.isHuman && - (relation.relation === 'trusts' || relation.relation === 'mutuallyTrusts')) { - humanInviters.push(relation); - } - } - - return humanInviters; - } } \ No newline at end of file diff --git a/packages/sdk/src/v2/v2Avatar.ts b/packages/sdk/src/v2/v2Avatar.ts index 222a526..fa457e4 100644 --- a/packages/sdk/src/v2/v2Avatar.ts +++ b/packages/sdk/src/v2/v2Avatar.ts @@ -434,9 +434,4 @@ export class V2Avatar implements AvatarInterfaceV2 { throw new Error('Name registry is not available'); } } - - // We don't return any inviter if avatar is already in v2 - async getInviters(): Promise { - return []; - } } \ No newline at end of file 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 4/4] 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.