From ef4742c614c34d6e907d3352cdcbd7e44ebf62a2 Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 23 May 2024 15:26:17 -0500 Subject: [PATCH 1/9] add passport score to streams minidonations --- src/services/recurringDonationService.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/services/recurringDonationService.ts b/src/services/recurringDonationService.ts index 284174b6c..00149a01d 100644 --- a/src/services/recurringDonationService.ts +++ b/src/services/recurringDonationService.ts @@ -221,7 +221,9 @@ export const createRelatedDonationsToStream = async ( activeQfRoundForProject && activeQfRoundForProject.isEligibleNetwork(networkId) ) { + const projectOwner = await User.findOneBy({ id: project.adminUserId }); donation.qfRound = activeQfRoundForProject; + donation.qfRoundUserScore = projectOwner?.passportScore; } const { givbackFactor, projectRank, bottomRankInRound, powerRound } = From 8d8c04e5bf725d6f6960c7c024932fcc40168665 Mon Sep 17 00:00:00 2001 From: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> Date: Mon, 27 May 2024 11:03:40 -0500 Subject: [PATCH 2/9] Hotfix performance improvements (#1587) * add migration and columns for totals in project entity (#1571) * add migration and columns for totals in project entity * add service to update project totals * add filling the value for new fields * Add methods for adding matching to totals usd received (#1582) * add methods for adding matching to totals usd received * Update src/repositories/qfRoundHistoryRepository.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: mohammadranjbarz Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * add coalesce to get 0 always incase null total matching --------- Co-authored-by: mohammadranjbarz Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- ...7-AddCalculatedFieldAsColumnsForProject.ts | 52 +++++++++++++++++++ src/entities/project.ts | 41 +++------------ src/repositories/qfRoundHistoryRepository.ts | 17 ++++++ .../projectResolver.allProject.test.ts | 8 +-- src/services/donationService.ts | 3 ++ src/services/projectService.ts | 42 +++++++++++++++ 6 files changed, 126 insertions(+), 37 deletions(-) create mode 100644 migration/1715728347907-AddCalculatedFieldAsColumnsForProject.ts diff --git a/migration/1715728347907-AddCalculatedFieldAsColumnsForProject.ts b/migration/1715728347907-AddCalculatedFieldAsColumnsForProject.ts new file mode 100644 index 000000000..2c6a01a8a --- /dev/null +++ b/migration/1715728347907-AddCalculatedFieldAsColumnsForProject.ts @@ -0,0 +1,52 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddCalculatedFieldAsColumnsForProject1715728347907 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE "project" + ADD COLUMN IF NOT EXISTS "sumDonationValueUsdForActiveQfRound" DOUBLE PRECISION DEFAULT 0; + `); + + await queryRunner.query(` + ALTER TABLE "project" + ADD COLUMN IF NOT EXISTS "sumDonationValueUsd" DOUBLE PRECISION DEFAULT 0; + `); + + // Add new integer columns for counting unique donors with 'IF NOT EXISTS' + await queryRunner.query(` + ALTER TABLE "project" + ADD COLUMN IF NOT EXISTS "countUniqueDonorsForActiveQfRound" INTEGER DEFAULT 0; + `); + + await queryRunner.query(` + ALTER TABLE "project" + ADD COLUMN IF NOT EXISTS "countUniqueDonors" INTEGER DEFAULT 0; + `); + + await queryRunner.query(` + UPDATE "project" + SET "countUniqueDonors" = pds."uniqueDonorsCount", + "sumDonationValueUsd" = pds."sumVerifiedDonations" + FROM "project_donation_summary_view" AS pds + WHERE "project"."id" = pds."projectId"; + `); + } + + public async down(queryRunner: QueryRunner): Promise { + // Use 'IF EXISTS' in the DROP statement to avoid errors in case the column does not exist + await queryRunner.query( + `ALTER TABLE "project" DROP COLUMN IF EXISTS "sumDonationValueUsdForActiveQfRound"`, + ); + await queryRunner.query( + `ALTER TABLE "project" DROP COLUMN IF EXISTS "sumDonationValueUsd"`, + ); + await queryRunner.query( + `ALTER TABLE "project" DROP COLUMN IF EXISTS "countUniqueDonorsForActiveQfRound"`, + ); + await queryRunner.query( + `ALTER TABLE "project" DROP COLUMN IF EXISTS "countUniqueDonors"`, + ); + } +} diff --git a/src/entities/project.ts b/src/entities/project.ts index 109ebbe29..479a12979 100644 --- a/src/entities/project.ts +++ b/src/entities/project.ts @@ -40,12 +40,6 @@ import { Category } from './category'; import { FeaturedUpdate } from './featuredUpdate'; import { getHtmlTextSummary } from '../utils/utils'; import { QfRound } from './qfRound'; -import { - countUniqueDonors, - countUniqueDonorsForRound, - sumDonationValueUsd, - sumDonationValueUsdForQfRound, -} from '../repositories/donationRepository'; import { getProjectDonationsSqrtRootSum, getQfRoundTotalProjectsDonationsSum, @@ -474,41 +468,22 @@ export class Project extends BaseEntity { createdAt: new Date(), }).save(); } - /** - * Custom Query Builders to chain together - */ @Field(_type => Float, { nullable: true }) - async sumDonationValueUsdForActiveQfRound() { - const activeQfRound = this.getActiveQfRound(); - return activeQfRound - ? await sumDonationValueUsdForQfRound({ - projectId: this.id, - qfRoundId: activeQfRound.id, - }) - : 0; - } + @Column({ type: 'float', nullable: true }) + sumDonationValueUsdForActiveQfRound: number; @Field(_type => Float, { nullable: true }) - async sumDonationValueUsd() { - return await sumDonationValueUsd(this.id); - } + @Column({ type: 'float', nullable: true }) + sumDonationValueUsd: number; @Field(_type => Int, { nullable: true }) - async countUniqueDonorsForActiveQfRound() { - const activeQfRound = this.getActiveQfRound(); - return activeQfRound - ? await countUniqueDonorsForRound({ - projectId: this.id, - qfRoundId: activeQfRound.id, - }) - : 0; - } + @Column({ type: 'int', nullable: true }) + countUniqueDonorsForActiveQfRound: number; @Field(_type => Int, { nullable: true }) - async countUniqueDonors() { - return await countUniqueDonors(this.id); - } + @Column({ type: 'int', nullable: true }) + countUniqueDonors: number; // In your main class @Field(_type => EstimatedMatching, { nullable: true }) diff --git a/src/repositories/qfRoundHistoryRepository.ts b/src/repositories/qfRoundHistoryRepository.ts index cfa25ccf8..7a04d7c6b 100644 --- a/src/repositories/qfRoundHistoryRepository.ts +++ b/src/repositories/qfRoundHistoryRepository.ts @@ -39,6 +39,23 @@ export const getQfRoundHistory = async (params: { return QfRoundHistory.findOne({ where: { projectId, qfRoundId } }); }; +export const getQfRoundHistoryMatchingValueUsd = async ( + projectId: number, +): Promise => { + try { + logger.debug('Executing query to fetch matching fund values'); + const result = await QfRoundHistory.createQueryBuilder('q') + .select('COALESCE(SUM(q."matchingFundPriceUsd"),0)', 'total') + .where('q.projectId = :projectId', { projectId }) + .getRawOne(); + + return result.total; + } catch (e) { + logger.error('Error in getQfRoundHistoryMatchingValueUsd', e); + throw e; + } +}; + export const getQfRoundHistoriesThatDontHaveRelatedDonations = async (): Promise => { try { diff --git a/src/resolvers/projectResolver.allProject.test.ts b/src/resolvers/projectResolver.allProject.test.ts index 025dbb9d6..7d84cf2d9 100644 --- a/src/resolvers/projectResolver.allProject.test.ts +++ b/src/resolvers/projectResolver.allProject.test.ts @@ -79,10 +79,10 @@ function allProjectsTestCases() { getHtmlTextSummary(project.description), ); assert.isNull(project.estimatedMatching); - assert.exists(project.sumDonationValueUsd); - assert.exists(project.sumDonationValueUsdForActiveQfRound); - assert.exists(project.countUniqueDonorsForActiveQfRound); - assert.exists(project.countUniqueDonors); + assert.isNull(project.sumDonationValueUsd); + assert.isNull(project.sumDonationValueUsdForActiveQfRound); + assert.isNull(project.countUniqueDonorsForActiveQfRound); + assert.isNull(project.countUniqueDonors); }); }); diff --git a/src/services/donationService.ts b/src/services/donationService.ts index d9d4a7c66..28972396d 100644 --- a/src/services/donationService.ts +++ b/src/services/donationService.ts @@ -44,6 +44,7 @@ import { getTransactionInfoFromNetwork } from './chains'; import { getEvmTransactionTimestamp } from './chains/evm/transactionService'; import { getOrttoPersonAttributes } from '../adapters/notifications/NotificationCenterAdapter'; import { CustomToken, getTokenPrice } from './priceService'; +import { updateProjectStatistics } from './projectService'; export const TRANSAK_COMPLETED_STATUS = 'COMPLETED'; @@ -369,6 +370,8 @@ export const syncDonationStatusWithBlockchainNetwork = async (params: { await refreshProjectEstimatedMatchingView(); await refreshProjectDonationSummaryView(); + await updateProjectStatistics(donation.projectId); + const donationStats = await getUserDonationStats(donation.userId); const donor = await findUserById(donation.userId); diff --git a/src/services/projectService.ts b/src/services/projectService.ts index e360c5235..108f41811 100644 --- a/src/services/projectService.ts +++ b/src/services/projectService.ts @@ -1,4 +1,12 @@ import { Project } from '../entities/project'; +import { + countUniqueDonors, + countUniqueDonorsForRound, + sumDonationValueUsd, + sumDonationValueUsdForQfRound, +} from '../repositories/donationRepository'; +import { findProjectById } from '../repositories/projectRepository'; +import { getQfRoundHistoryMatchingValueUsd } from '../repositories/qfRoundHistoryRepository'; export const getAppropriateSlug = async ( slugBase: string, @@ -22,6 +30,40 @@ export const getAppropriateSlug = async ( return slug; }; +export const updateProjectStatistics = async (projectId: number) => { + const project = await findProjectById(projectId); + if (!project) return; + + const activeQfRound = project.getActiveQfRound(); + if (activeQfRound) { + project.sumDonationValueUsdForActiveQfRound = + await sumDonationValueUsdForQfRound({ + projectId: project.id, + qfRoundId: activeQfRound.id, + }); + project.countUniqueDonorsForActiveQfRound = await countUniqueDonorsForRound( + { + projectId: project.id, + qfRoundId: activeQfRound.id, + }, + ); + } + + if (!activeQfRound) { + project.sumDonationValueUsdForActiveQfRound = 0; + project.countUniqueDonorsForActiveQfRound = 0; + } + + const projectQfRoundHistoryMatching = await getQfRoundHistoryMatchingValueUsd( + project.id, + ); + + project.sumDonationValueUsd = + (await sumDonationValueUsd(project.id)) + projectQfRoundHistoryMatching; + project.countUniqueDonors = await countUniqueDonors(project.id); + await project.save(); +}; + // Current Formula: will be changed possibly in the future export const getQualityScore = (description, hasImageUpload, heartCount?) => { const heartScore = 10; From 63a58b83451cde80f9e9cc059841e0c515ac0403 Mon Sep 17 00:00:00 2001 From: Ramin Date: Wed, 22 May 2024 18:00:26 +0330 Subject: [PATCH 3/9] add word similarity to search --- src/resolvers/projectResolver.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/resolvers/projectResolver.ts b/src/resolvers/projectResolver.ts index e68fc17e8..456d37b6c 100644 --- a/src/resolvers/projectResolver.ts +++ b/src/resolvers/projectResolver.ts @@ -329,13 +329,13 @@ export class ProjectResolver { // .setParameter('searchTerm', searchTerm) .andWhere( new Brackets(qb => { - qb.where('project.title % :searchTerm ', { + qb.where('project.title %> :searchTerm ', { searchTerm, }) - .orWhere('project.description % :searchTerm ', { + .orWhere('project.description %> :searchTerm ', { searchTerm, }) - .orWhere('project.impactLocation % :searchTerm', { + .orWhere('project.impactLocation %> :searchTerm', { searchTerm, }); }), From 9b9bde5c8ab1e88bd7ee33ee6d1537a8af1adc2b Mon Sep 17 00:00:00 2001 From: Ramin Date: Wed, 22 May 2024 18:39:21 +0330 Subject: [PATCH 4/9] fix word similarity test case --- src/resolvers/projectResolver.allProject.test.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/resolvers/projectResolver.allProject.test.ts b/src/resolvers/projectResolver.allProject.test.ts index 7d84cf2d9..854400e34 100644 --- a/src/resolvers/projectResolver.allProject.test.ts +++ b/src/resolvers/projectResolver.allProject.test.ts @@ -51,23 +51,18 @@ import { ChainType } from '../types/network'; describe('all projects test cases --->', allProjectsTestCases); function allProjectsTestCases() { - it('should return projects search by owner', async () => { + it('should return projects search by title', async () => { const result = await axios.post(graphqlUrl, { query: fetchMultiFilterAllProjectsQuery, variables: { - searchTerm: SEED_DATA.SECOND_USER.name, + searchTerm: SEED_DATA.FIRST_PROJECT.title, }, }); const projects = result.data.data.allProjects.projects; - const secondUserProjects = await Project.find({ - where: { - adminUserId: SEED_DATA.SECOND_USER.id, - }, - }); - assert.equal(projects.length, secondUserProjects.length); - assert.equal(projects[0]?.adminUserId, SEED_DATA.SECOND_USER.id); + assert.isTrue(projects.length > 0); + assert.equal(projects[0]?.adminUserId, SEED_DATA.FIRST_PROJECT.adminUserId); assert.isNotEmpty(projects[0].addresses); projects.forEach(project => { assert.isNotOk(project.adminUser.email); From e547b048583beb51dcc6895dd101364664723323 Mon Sep 17 00:00:00 2001 From: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> Date: Wed, 29 May 2024 16:36:44 -0500 Subject: [PATCH 5/9] sync all donations totals (#1594) * sync all donations totals * add update qfroundmatching --- src/services/donationService.ts | 9 ++++++--- src/services/projectService.ts | 8 +------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/services/donationService.ts b/src/services/donationService.ts index 28972396d..d7a2dea41 100644 --- a/src/services/donationService.ts +++ b/src/services/donationService.ts @@ -191,9 +191,11 @@ export const updateTotalDonationsOfProject = async ( ` UPDATE "project" SET "totalDonations" = ( - SELECT COALESCE(SUM(d."valueUsd"),0) - FROM "donation" as d - WHERE d."projectId" = $1 AND d."status" = 'verified' + ( + SELECT COALESCE(SUM(d."valueUsd"),0) + FROM "donation" as d + WHERE d."projectId" = $1 AND d."status" = 'verified' + ) ) WHERE "id" = $1 `, @@ -367,6 +369,7 @@ export const syncDonationStatusWithBlockchainNetwork = async (params: { }); // Update materialized view for project and qfRound data + await insertDonationsFromQfRoundHistory(); await refreshProjectEstimatedMatchingView(); await refreshProjectDonationSummaryView(); diff --git a/src/services/projectService.ts b/src/services/projectService.ts index 108f41811..5dbc7265a 100644 --- a/src/services/projectService.ts +++ b/src/services/projectService.ts @@ -6,7 +6,6 @@ import { sumDonationValueUsdForQfRound, } from '../repositories/donationRepository'; import { findProjectById } from '../repositories/projectRepository'; -import { getQfRoundHistoryMatchingValueUsd } from '../repositories/qfRoundHistoryRepository'; export const getAppropriateSlug = async ( slugBase: string, @@ -54,12 +53,7 @@ export const updateProjectStatistics = async (projectId: number) => { project.countUniqueDonorsForActiveQfRound = 0; } - const projectQfRoundHistoryMatching = await getQfRoundHistoryMatchingValueUsd( - project.id, - ); - - project.sumDonationValueUsd = - (await sumDonationValueUsd(project.id)) + projectQfRoundHistoryMatching; + project.sumDonationValueUsd = await sumDonationValueUsd(project.id); project.countUniqueDonors = await countUniqueDonors(project.id); await project.save(); }; From 4c14552b4a694613f60edd03ba66eb6c25a17509 Mon Sep 17 00:00:00 2001 From: mohammadranjbarz Date: Fri, 31 May 2024 07:45:29 +0330 Subject: [PATCH 6/9] 4201 add sponser's donations manually (#1588) * Write migration for adding sponsor TXs manually to DB related to https://github.com/Giveth/giveth-dapps-v2/issues/4201 * Add donor addresses for sponser's donations related to https://github.com/Giveth/giveth-dapps-v2/issues/4201#issuecomment-2133034923 * Fix eslint errors * Fix eslint errors * Fix eslint errors * Fix eslint errors * Add some sponser donations * Change networkId of some donations to optimism --- ...549958362-add_donations_mannually_to_db.ts | 413 ++++++++++++++++++ 1 file changed, 413 insertions(+) create mode 100644 migration/1716549958362-add_donations_mannually_to_db.ts diff --git a/migration/1716549958362-add_donations_mannually_to_db.ts b/migration/1716549958362-add_donations_mannually_to_db.ts new file mode 100644 index 000000000..a9348ff8f --- /dev/null +++ b/migration/1716549958362-add_donations_mannually_to_db.ts @@ -0,0 +1,413 @@ +import moment from 'moment'; +import { MigrationInterface, QueryRunner } from 'typeorm'; +import { Donation } from '../src/entities/donation'; +import { NETWORK_IDS } from '../src/provider'; +import config from '../src/config'; +import { AppDataSource } from '../src/orm'; +import { findProjectById } from '../src/repositories/projectRepository'; +import { Project } from '../src/entities/project'; +import { calculateGivbackFactor } from '../src/services/givbackService'; +import { + updateUserTotalDonated, + updateUserTotalReceived, +} from '../src/services/userService'; +import { updateTotalDonationsOfProject } from '../src/services/donationService'; +import { + refreshProjectDonationSummaryView, + refreshProjectEstimatedMatchingView, +} from '../src/services/projectViewsService'; +import { updateProjectStatistics } from '../src/services/projectService'; + +const millisecondTimestampToDate = (timestamp: number): Date => { + return new Date(timestamp); +}; + +// Use below query to find project by toWalletAddress +/** + SELECT p.*, p.slug + FROM "project" p + WHERE p."id" IN ( + SELECT pa."projectId" + FROM "project_address" pa + WHERE lower(pa."address") = lower('0x6e8873085530406995170da467010565968c7c62') + ); + + */ + +const transactions: (Partial & { + donorName?: string; + donorAddress?: string; +})[] = [ + // https://github.com/Giveth/giveth-dapps-v2/issues/4201 + + // https://optimistic.etherscan.io/tx/0xd5b98a3a6a928c944514c4bb7550c7a2c49b4592af7d4e0e06ea66f530fd8211 + { + // LottoPGF + donorName: 'LottoPGF', + donorAddress: '0x77fb4fa1ABA92576942aD34BC47834059b84e693', + fromWalletAddress: '0x437A4909293e704bB090357d714b585bF5658C4e', + toWalletAddress: '0x6e8873085530406995170da467010565968c7c62', + // https://giveth.io/project/giveth-matching-pool-0 + projectId: 1443, + transactionId: + '0xd5b98a3a6a928c944514c4bb7550c7a2c49b4592af7d4e0e06ea66f530fd8211', + currency: 'ETH', + tokenAddress: '0x0000000000000000000000000000000000000000', + amount: 0.49, + valueUsd: 1462.7, + transactionNetworkId: NETWORK_IDS.OPTIMISTIC, + createdAt: millisecondTimestampToDate(1713354817000), + }, + + // https://arbiscan.io/tx/0x22e9a6665bdd3b4d0bca76f2fc4db587d27e646a8933cfa4d0aca8b08715c3d3 + { + // GMX + donorName: 'GMX', + fromWalletAddress: '0xb1F3D086b7c5114F429dc48530C7A0a20a8B65CE', + donorAddress: '0x6da54f64d189a3cd68d1b7ab016ddabd112ad01f', + toWalletAddress: '0x6e8873085530406995170da467010565968c7c62', + // https://giveth.io/project/giveth-matching-pool-0 + projectId: 1443, + transactionId: + '0x22e9a6665bdd3b4d0bca76f2fc4db587d27e646a8933cfa4d0aca8b08715c3d3', + currency: 'USDC', + tokenAddress: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + amount: 7500, + valueUsd: 7500, + // transactionNetworkId: NETWORK_IDS.ARBITRUM_MAINNET, + transactionNetworkId: NETWORK_IDS.OPTIMISTIC, + createdAt: millisecondTimestampToDate(1714247188000), + }, + + // https://arbiscan.io/tx/0x38e060142c75fa4f3d2eefd27556ef899b3a6faa61bbd842ac7b06cfdd5fad2f + //TODO I set the network for all these donations to OP to make sure givbacks will distribute on OP + // but later we shuuld change it back to the right network + { + // Premia + donorName: 'Premia', + fromWalletAddress: '0xfc5538E1E9814eD6487b407FaD7b5710739A1cC2', + donorAddress: '0x5ca1ea5549e4e7cb64ae35225e11865d2572b3f9', + toWalletAddress: '0x6e8873085530406995170da467010565968c7c62', + // https://giveth.io/project/giveth-matching-pool-0 + projectId: 1443, + transactionId: + '0x38e060142c75fa4f3d2eefd27556ef899b3a6faa61bbd842ac7b06cfdd5fad2f', + currency: 'USDC.e', + tokenAddress: '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8', + amount: 1500, + valueUsd: 1500, + transactionNetworkId: NETWORK_IDS.OPTIMISTIC, + // transactionNetworkId: NETWORK_IDS.ARBITRUM_MAINNET, + createdAt: millisecondTimestampToDate(1713595569000), + }, + + // https://arbiscan.io/tx/0x5624a9d6b1894c6275827864b34396a89f65dd07fa0ba2d9b48e170a5bbe14c3 + { + // MUX + donorName: 'MUX', + fromWalletAddress: '0x7C8126ef43c09C22bf0CcdF7426180e6c48068A5', + donorAddress: '0xf2a26c73f52c903d21ad85626b344d48e7af72ee', + toWalletAddress: '0x6e8873085530406995170da467010565968c7c62', + // https://giveth.io/project/giveth-matching-pool-0 + projectId: 1443, + transactionId: + '0x5624a9d6b1894c6275827864b34396a89f65dd07fa0ba2d9b48e170a5bbe14c3', + currency: 'USDC', + tokenAddress: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + amount: 1500, + valueUsd: 1500, + transactionNetworkId: NETWORK_IDS.OPTIMISTIC, + // transactionNetworkId: NETWORK_IDS.ARBITRUM_MAINNET, + createdAt: millisecondTimestampToDate(1713844247000), + }, + + // https://arbiscan.io/tx/0x3bc748bc39ae433a083a54b073461d263919bdcf52746197d335586c86ab2d46 + { + // Rage Trade + donorName: 'Rage Trade', + fromWalletAddress: '0x507c7777837B85EDe1e67f5A4554dDD7e58b1F87', + donorAddress: '0x507c7777837b85ede1e67f5a4554ddd7e58b1f87', + toWalletAddress: '0x6e8873085530406995170da467010565968c7c62', + // https://giveth.io/project/giveth-matching-pool-0 + projectId: 1443, + transactionId: + '0x3bc748bc39ae433a083a54b073461d263919bdcf52746197d335586c86ab2d46', + currency: 'USDC.e', + tokenAddress: '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8', + amount: 1500, + valueUsd: 1500, + transactionNetworkId: NETWORK_IDS.OPTIMISTIC, + // transactionNetworkId: NETWORK_IDS.ARBITRUM_MAINNET, + createdAt: millisecondTimestampToDate(1714074373000), + }, + + // https://arbiscan.io/tx/0x83963132bee77a7a06ed8ebe1fc2557fea9971f1fb5482d62e145b3d1f0bec73 + { + // Dodo + donorName: 'Dodo', + fromWalletAddress: '0x01b6c66dee0476B70938Cf87Fe372848C58b6a13', + donorAddress: '0x28C6c06298d514Db089934071355E5743bf21d60', + toWalletAddress: '0x6e8873085530406995170da467010565968c7c62', + // https://giveth.io/project/giveth-matching-pool-0 + projectId: 1443, + transactionId: + '0x83963132bee77a7a06ed8ebe1fc2557fea9971f1fb5482d62e145b3d1f0bec73', + currency: 'USDT', + tokenAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + amount: 1500, + valueUsd: 1500, + transactionNetworkId: NETWORK_IDS.OPTIMISTIC, + // transactionNetworkId: NETWORK_IDS.MAIN_NET, + createdAt: millisecondTimestampToDate(1714044191000), + }, + + // https://arbiscan.io/tx/0xe3bc8e163001ec898a0c27be4fd1802b747b1d2b1c79e2ff404ac91d6f9d239a + { + // Gitcoin (Kyle) + donorName: 'Kyle', + fromWalletAddress: '0x202d0b551f0e137Efb419e70e1776B6d578bdbF3', + donorAddress: '0x563537412ad5d49faa7fa442b9193b8238d98c3c', + toWalletAddress: '0x6e8873085530406995170da467010565968c7c62', + // https://giveth.io/project/giveth-matching-pool-0 + projectId: 1443, + transactionId: + '0xe3bc8e163001ec898a0c27be4fd1802b747b1d2b1c79e2ff404ac91d6f9d239a', + currency: 'USDT', + tokenAddress: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + amount: 7500, + valueUsd: 7500, + transactionNetworkId: NETWORK_IDS.OPTIMISTIC, + // transactionNetworkId: NETWORK_IDS.MAIN_NET, + createdAt: millisecondTimestampToDate(1714052806000), + }, + + // https://arbiscan.io/tx/0xa8962021b121f4f2b6bd8572ec450d0d81862c5284b5ddc8c851d0ecb3afb499 + { + // WOOFi + donorName: 'WOOFi', + fromWalletAddress: '0x7C8126ef43c09C22bf0CcdF7426180e6c48068A5', + donorAddress: '0x63dfe4e34a3bfc00eb0220786238a7c6cef8ffc4', + toWalletAddress: '0x6e8873085530406995170da467010565968c7c62', + // https://giveth.io/project/giveth-matching-pool-0 + projectId: 1443, + transactionId: + '0xa8962021b121f4f2b6bd8572ec450d0d81862c5284b5ddc8c851d0ecb3afb499', + currency: 'USDC', + tokenAddress: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + amount: 1500, + valueUsd: 1500, + transactionNetworkId: NETWORK_IDS.OPTIMISTIC, + // transactionNetworkId: NETWORK_IDS.ARBITRUM_MAINNET, + createdAt: millisecondTimestampToDate(1714033588000), + }, + + // https://github.com/Giveth/impact-graph/issues/1580 + // https://etherscan.io/tx/0x12d699a9c3eb3605aebbeeea9453286a16d2772738a837efe36cd4a891f75893 + { + // Landeck + donorName: 'Landeck', + fromWalletAddress: '0x659C5827EED31F205876F5A473cdd7e6B6AF1049', + donorAddress: '0x659C5827EED31F205876F5A473cdd7e6B6AF1049', + toWalletAddress: '0xd0057c59A091eec3C825fF73F7065020baEE3680', + // https://giveth.io/project/emergency-relief-fund-for-brazil-floods + projectId: 3461, + transactionId: + '0x12d699a9c3eb3605aebbeeea9453286a16d2772738a837efe36cd4a891f75893', + currency: 'ETH', + tokenAddress: '0x0000000000000000000000000000000000000000', + + // Galactic Giving qfRound + qfRoundId: 9, + amount: 0.011389, + valueUsd: 33.86, + transactionNetworkId: NETWORK_IDS.OPTIMISTIC, + // transactionNetworkId: NETWORK_IDS.MAIN_NET, + createdAt: millisecondTimestampToDate(1715201051000), + }, + + // https://github.com/Giveth/giveth-dapps-v2/issues/4203 + + // https://arbiscan.io/tx/0xd9bf19eb3c09baf79159e772ba0fd824b812d5953a7e2d026a2d65966501c7b3 + { + // GloDollar + donorName: 'GloDollar', + donorAddress: '0x1bbfc95b826693bf17665f36a66ac9c389b7e581', + fromWalletAddress: '0x1bbfc95b826693bf17665f36a66ac9c389b7e581', + toWalletAddress: '0x6e8873085530406995170da467010565968c7c62', + // https://giveth.io/project/giveth-matching-pool-0 + projectId: 1443, + transactionId: + '0xd9bf19eb3c09baf79159e772ba0fd824b812d5953a7e2d026a2d65966501c7b3', + currency: 'USDGLO', + tokenAddress: '0x4F604735c1cF31399C6E711D5962b2B3E0225AD3', + amount: 1500, + valueUsd: 1500, + transactionNetworkId: NETWORK_IDS.ARBITRUM_MAINNET, + createdAt: millisecondTimestampToDate(1713273778000), + }, + + // https://etherscan.io/tx/0x4b0b0e7b8137ac68e42ebfa170607e6b59015d3583e7290af081ea974cfd6b10 + { + // Aragon Project + donorName: 'Aragon Project', + donorAddress: '0x124cc44b7119fb592a774f466823f31885b60440', + fromWalletAddress: '0xD6B270DFEE268B452c86251Fd7e12Db8dE9200FB', + toWalletAddress: '0x6e8873085530406995170da467010565968c7c62', + // https://giveth.io/project/giveth-matching-pool-0 + projectId: 1443, + transactionId: + '0x4b0b0e7b8137ac68e42ebfa170607e6b59015d3583e7290af081ea974cfd6b10', + currency: 'USDC', + tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + amount: 1000, + valueUsd: 1000, + transactionNetworkId: NETWORK_IDS.MAIN_NET, + createdAt: millisecondTimestampToDate(1696867907000), + }, + + // https://etherscan.io/tx/0x12135b286cbcb71c1c4155ee650613e1840d54619d4d06ae7be77f17bdc4683b + { + // Aragon Project + donorName: 'Aragon Project', + donorAddress: '0x124cc44b7119fb592a774f466823f31885b60440', + fromWalletAddress: '0xD6B270DFEE268B452c86251Fd7e12Db8dE9200FB', + toWalletAddress: '0x6e8873085530406995170da467010565968c7c62', + // https://giveth.io/project/giveth-matching-pool-0 + projectId: 1443, + transactionId: + '0x12135b286cbcb71c1c4155ee650613e1840d54619d4d06ae7be77f17bdc4683b', + currency: 'USDC', + tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + amount: 5000, + valueUsd: 5000, + transactionNetworkId: NETWORK_IDS.MAIN_NET, + createdAt: millisecondTimestampToDate(1689342251000), + }, + + // https://etherscan.io/tx/0x30954cb441cb7b2184e6cd1afc6acbd1318f86a68b669f6bfb2786dd459e2d6c + { + // Public Nouns + donorName: 'Public Nouns', + donorAddress: '0x553826cb0d0ee63155920f42b4e60aae6607dfcb', + fromWalletAddress: '0xda04c025F4d8Ac555Fdb3497B197D28FCEcf4d41', + toWalletAddress: '0x6e8873085530406995170da467010565968c7c62', + // https://giveth.io/project/giveth-matching-pool-0 + projectId: 1443, + transactionId: + '0x30954cb441cb7b2184e6cd1afc6acbd1318f86a68b669f6bfb2786dd459e2d6c', + currency: 'ETH', + tokenAddress: '0x0000000000000000000000000000000000000000', + amount: 5, + valueUsd: 9458.4, + transactionNetworkId: NETWORK_IDS.MAIN_NET, + createdAt: millisecondTimestampToDate(1689897227000), + }, + + // https://etherscan.io/tx/0x10975407db91205cda1e9f9bb288488d215d6d94dfa12b192ffa0cb78893df11 + { + // Public Nouns + donorName: 'Public Nouns', + donorAddress: '0x553826cb0d0ee63155920f42b4e60aae6607dfcb', + fromWalletAddress: '0xda04c025F4d8Ac555Fdb3497B197D28FCEcf4d41', + toWalletAddress: '0x6e8873085530406995170da467010565968c7c62', + // https://giveth.io/project/giveth-matching-pool-0 + projectId: 1443, + transactionId: + '0x10975407db91205cda1e9f9bb288488d215d6d94dfa12b192ffa0cb78893df11', + currency: 'ETH', + tokenAddress: '0x0000000000000000000000000000000000000000', + amount: 5, + valueUsd: 11154.7, + transactionNetworkId: NETWORK_IDS.MAIN_NET, + createdAt: millisecondTimestampToDate(1703626727000), + }, + + // https://etherscan.io/tx/0xa6e68136fdb972597cb795d73059aa5a7eedfe5a84b0af3b0091121231e1529d + { + // Jordi Baylina + donorName: 'Jordi Baylina', + donorAddress: '0x1DBA1131000664b884A1Ba238464159892252D3a', + fromWalletAddress: '0x1dba1131000664b884a1ba238464159892252d3a', + toWalletAddress: '0x6e8873085530406995170da467010565968c7c62', + // https://giveth.io/project/giveth-matching-pool-0 + projectId: 1443, + transactionId: + '0xa6e68136fdb972597cb795d73059aa5a7eedfe5a84b0af3b0091121231e1529d', + currency: 'ENS', + tokenAddress: '0xc18360217d8f7ab5e7c516566761ea12ce7f9d72', + amount: 110, + valueUsd: 2739, + transactionNetworkId: NETWORK_IDS.MAIN_NET, + createdAt: millisecondTimestampToDate(1689200507000), + }, +]; + +export class AddDonationsMannuallyToDb1716549958362 + implements MigrationInterface +{ + async up(queryRunner: QueryRunner): Promise { + const environment = config.get('ENVIRONMENT') as string; + + if (environment !== 'production') { + // eslint-disable-next-line no-console + console.log('We just want to create these donations in production DB'); + return; + } + + await AppDataSource.initialize(); + for (const tx of transactions) { + let user = ( + await queryRunner.query(`SELECT * FROM public.user + WHERE lower("walletAddress")=lower('${tx.donorAddress}')`) + )[0]; + if (!user) { + // eslint-disable-next-line no-console + console.log('User is not in our DB, creating .... '); + await queryRunner.query(` + INSERT INTO public.user ("walletAddress", role,"loginType", name) + VALUES('${tx?.donorAddress?.toLowerCase()}', 'restricted','wallet', '${tx.donorName}'); + `); + user = ( + await queryRunner.query(`SELECT * FROM public.user + WHERE lower("walletAddress")=lower('${tx.donorAddress}')`) + )[0]; + } + + // Set true for isTokenEligibleForGivback, isProjectVerified because Ashley mentioned we want to pay givback for them + const createdAt = moment(tx.createdAt).format('YYYY-MM-DD HH:mm:ss'); + const project = (await findProjectById( + tx.projectId as number, + )) as Project; + + const { givbackFactor, projectRank, powerRound, bottomRankInRound } = + await calculateGivbackFactor(tx.projectId as number); + + await queryRunner.query(` + INSERT INTO donation ("toWalletAddress", "projectId", "fromWalletAddress", "userId", amount, currency, "transactionId", "transactionNetworkId", anonymous, "valueUsd", status, + "segmentNotified", "isTokenEligibleForGivback", "isProjectVerified", "createdAt", "givbackFactor", "powerRound", "projectRank", "bottomRankInRound", "qfRoundId", "tokenAddress") + VALUES ('${tx.toWalletAddress?.toLowerCase()}', ${ + tx.projectId + }, '${tx.fromWalletAddress?.toLowerCase()}', ${user.id}, ${ + tx.amount + }, '${tx.currency}', '${tx.transactionId?.toLowerCase()}', ${ + tx.transactionNetworkId + }, false, ${tx.valueUsd}, 'verified', + true, true, true, '${createdAt}', ${givbackFactor}, ${powerRound}, ${projectRank}, ${bottomRankInRound}, ${tx.qfRoundId || null}, '${ + tx.tokenAddress + }'); + `); + + await updateUserTotalDonated(user.id); + await updateUserTotalReceived(project.adminUser?.id); + await updateTotalDonationsOfProject(tx.projectId as number); + await updateProjectStatistics(tx.projectId as number); + } + + await refreshProjectEstimatedMatchingView(); + await refreshProjectDonationSummaryView(); + } + + async down(_queryRunner: QueryRunner): Promise { + // + } +} From 3cab296c1983e1980b7ad6be37cfe244b22c36ca Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Fri, 31 May 2024 08:09:49 +0330 Subject: [PATCH 7/9] Fix migration to do nothing on conflict --- ...549958362-add_donations_mannually_to_db.ts | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/migration/1716549958362-add_donations_mannually_to_db.ts b/migration/1716549958362-add_donations_mannually_to_db.ts index a9348ff8f..20f5be750 100644 --- a/migration/1716549958362-add_donations_mannually_to_db.ts +++ b/migration/1716549958362-add_donations_mannually_to_db.ts @@ -381,21 +381,18 @@ export class AddDonationsMannuallyToDb1716549958362 const { givbackFactor, projectRank, powerRound, bottomRankInRound } = await calculateGivbackFactor(tx.projectId as number); - await queryRunner.query(` - INSERT INTO donation ("toWalletAddress", "projectId", "fromWalletAddress", "userId", amount, currency, "transactionId", "transactionNetworkId", anonymous, "valueUsd", status, - "segmentNotified", "isTokenEligibleForGivback", "isProjectVerified", "createdAt", "givbackFactor", "powerRound", "projectRank", "bottomRankInRound", "qfRoundId", "tokenAddress") - VALUES ('${tx.toWalletAddress?.toLowerCase()}', ${ - tx.projectId - }, '${tx.fromWalletAddress?.toLowerCase()}', ${user.id}, ${ - tx.amount - }, '${tx.currency}', '${tx.transactionId?.toLowerCase()}', ${ - tx.transactionNetworkId - }, false, ${tx.valueUsd}, 'verified', - true, true, true, '${createdAt}', ${givbackFactor}, ${powerRound}, ${projectRank}, ${bottomRankInRound}, ${tx.qfRoundId || null}, '${ - tx.tokenAddress - }'); - `); + INSERT INTO donation ("toWalletAddress", "projectId", "fromWalletAddress", "userId", amount, currency, "transactionId", "transactionNetworkId", anonymous, "valueUsd", status, + "segmentNotified", "isTokenEligibleForGivback", "isProjectVerified", "createdAt", "givbackFactor", "powerRound", "projectRank", "bottomRankInRound", "qfRoundId", "tokenAddress") + VALUES ('${tx.toWalletAddress?.toLowerCase()}', ${tx.projectId}, '${tx.fromWalletAddress?.toLowerCase()}', ${user.id}, ${tx.amount}, '${tx.currency}', '${tx.transactionId?.toLowerCase()}', ${ + tx.transactionNetworkId + }, false, ${tx.valueUsd}, 'verified', + true, true, true, '${createdAt}', ${givbackFactor}, ${powerRound}, ${projectRank}, ${bottomRankInRound}, ${tx.qfRoundId || null}, '${ + tx.tokenAddress + }') + ON CONFLICT DO NOTHING; + `); + await updateUserTotalDonated(user.id); await updateUserTotalReceived(project.adminUser?.id); From 42a603b2a1f2dc1c276a07eb55273230b7fb261d Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Fri, 31 May 2024 08:13:37 +0330 Subject: [PATCH 8/9] Fix eslint errors --- migration/1716549958362-add_donations_mannually_to_db.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/migration/1716549958362-add_donations_mannually_to_db.ts b/migration/1716549958362-add_donations_mannually_to_db.ts index 20f5be750..92c9b460b 100644 --- a/migration/1716549958362-add_donations_mannually_to_db.ts +++ b/migration/1716549958362-add_donations_mannually_to_db.ts @@ -393,7 +393,6 @@ export class AddDonationsMannuallyToDb1716549958362 ON CONFLICT DO NOTHING; `); - await updateUserTotalDonated(user.id); await updateUserTotalReceived(project.adminUser?.id); await updateTotalDonationsOfProject(tx.projectId as number); From 6d3d73c7fe2e8979c957435efea46219484c21b3 Mon Sep 17 00:00:00 2001 From: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> Date: Sun, 2 Jun 2024 04:42:46 -0500 Subject: [PATCH 9/9] Release upgrades + Base integration (#1586) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add activeQfRoundId to sortingBy InstantBoosting * add orderBy totalDonations and totalReactions * feat: add getRecurringDonationStats resolver * fix filtering by QF * remove qfRounds joins for non qf round filters * add some temp logs * remove temp logs * fix: changes test cases of recuring donations stats - create recored with createdAt field in past so test result won't be related to other endpoints test cases * Fix projectActualserviceView * fix stream balance depleted issue (#1496) Co-authored-by: mohammadranjbarz * rebuild * refresh and fetch user address separately (#1499) * Added pg_trgm extension migration (#1502) * fix: change recurring donations stats query to single query * fix recurring donation count * WIP: projectIds textArea * fix actual matching cap (#1507) * fix query error * fix user donations count * fix recurring donation count tests * fix user recurring donation query * fix user recurring donation test * add donations relation to qfround * add findArchivedQfRounds endpoint * add findArchivedQfRounds endpoint * feat: add sponsors & banner images upload * add sortBy to findArchivedQfRounds * 1.23.3 * add new test graphql query * add tests for new QfArchivedRounds * fixes on qfArchivedRounds query * add new tests for qfArchivedRounds query * fix findArchivedQfRounds tests * fix: keep already uploaded sponsors images * fix skip and limit for findArchivedQfRounds * Add logs and refactor the bootstrap code to help investigate latency problem * Add poolSize to orm config * Fix eslint errors * remove changing squareRootSumOfProjects when cap is overflown * Trigger ortto activity when user saves their profile info for the first time (#1520) * add newUser to updateUser query * add createOrttoProfile * add createOrttoProfile to NotificationAdapterInterface * add createOrttoProfile to MockNotificationAdapter * add CREATE_ORTTO_PROFILE event * Allow to set the matching pool token & amount to be something other than usd (#1517) * Allow to set the matching pool token & amount to be something other than USD in adminjs * Allow to set the matching pool token & amount to be something other than USD in QFRound table * add null to allocatedTokenSymbol and allocatedTokenChainId * add nullable true to allocatedTokenSymbol and allocatedTokenChainId * add allocatedFundUSDPreferred and allocatedFundUSD to qfRound * Comment migrations * Hotfix db improvements (#1523) * add extra configurations for postgresql connections * add master and slave replication strategy for typeorm * add qfRound to qfRoundStats * fix qfRoundStatsQuery * Hotfix staging fix latency (#1528) * add project donation summary view entity * convert projectQueries to querybuilder * add cache to projectDonationSummary queries * add configurable cache to slow queries * remove massive recurring donation log * add await for project queries * Add logs to projectVerificationForm * Add logs to projectVerificationForm * fix: add project Ids list textarea for qf round edit * Master to staging (#1543) * Hotfix db improvements (#1523) (#1524) * add extra configurations for postgresql connections * add master and slave replication strategy for typeorm Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> * Fix/db replica production (#1525) * Hotfix db improvements (#1523) * add extra configurations for postgresql connections * add master and slave replication strategy for typeorm * Define db read only configs --------- Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> * update comment * Hotfix latency issues for prod (#1529) * Hotfix staging fix latency (#1528) * add project donation summary view entity * convert projectQueries to querybuilder * add cache to projectDonationSummary queries * add configurable cache to slow queries * remove massive recurring donation log * add await for project queries * Add informative logs for draft donation service job (#1537) * Fix eslint errors * Fix/master test (#1541) * Fixed master test issue * Returned test to master pipeline * Comment executing donation summary view --------- Co-authored-by: Mohammad Ranjbar Z --------- Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> Co-authored-by: Carlos Co-authored-by: mohammadranjbarz * Fix/word similarity - staging (#1546) * Hotfix db improvements (#1523) (#1524) * add extra configurations for postgresql connections * add master and slave replication strategy for typeorm Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> * Fix/db replica production (#1525) * Hotfix db improvements (#1523) * add extra configurations for postgresql connections * add master and slave replication strategy for typeorm * Define db read only configs --------- Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> * update comment * Hotfix latency issues for prod (#1529) * Hotfix staging fix latency (#1528) * add project donation summary view entity * convert projectQueries to querybuilder * add cache to projectDonationSummary queries * add configurable cache to slow queries * remove massive recurring donation log * add await for project queries * Add informative logs for draft donation service job (#1537) * Fix eslint errors * Fix/master test (#1541) * Fixed master test issue * Returned test to master pipeline * Comment executing donation summary view --------- Co-authored-by: Mohammad Ranjbar Z * Fixed word similarity issue * Removed unused import --------- Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> Co-authored-by: Carlos Co-authored-by: mohammadranjbarz * remove refresh_project_summary_from totals and add user cache (#1539) * Remove users field from project * Remove users field from filterProjectsQuery * Remove projects field from user * remove added logs * fix eslint errors * remove users from Project.create * remove users from Project.create in testUtils.ts * remove projectOwnerId * replace admin with adminUserId * Add recurring donation join to donations() endpoint (#1554) related to #1483 * replace admin with adminUserId in SEED data * replace admin with adminUserId * replace admin with adminUserId in projectResolver.ts * replace admin with adminUserId in projectsTab.ts * replace admin with adminUserId in projectResolver.test.ts * replace admin with adminUserId in projectResolver.ts * add allocatedFundUSD and allocatedTokenSymbol to qfArchivedRounds * fix nullable * remove admin from project * replace admin with adminUserId * replace admin with adminUserId * add adminUserId field * drop admin column * Implementing read-only node replica in local * fix: add telegram to ProjectSocialMediaType enum to allow adding telegram url * Add some logs * Fix eslint errors * Add maxQueuedJobs for draft donation worker * Fix eslint errors * fix unstable test case * Disable concurrency for draft donation worker * add indexes to project_summary_view (#1568) * improve projectBySlug query * fix: change output types to float * Ignore small differences of amounts when matching draft donation for erc20 * add graphql-fields * add getVerificationFormStatusByProjectId * refactor projectBySlug * add findProjectIdBySlug * fix projectBySlug tests with new changes * fix projectBySlug tests with new changes * fix projectBySlug tests with new changes * remove projectVerificationForm assert * add logs * fix title in should return projects with indicated slug test * Add streamed mini donations to qf round (#1557) * Add streamed mini donations to qf round related to Giveth/giveth-dapps-v2#3284 * Fix eslint error * Fix eslint errors * make verificationFormStatus field public * Reduce test runnning time (#1574) * fix: reduce test runnning time * fix: add permissions test case * fix: add permissions test case * fix: refactor (excluding permissions test cases from global beforeEach) * add migration and columns for totals in project entity (#1571) * add migration and columns for totals in project entity * add service to update project totals * add filling the value for new fields * WIP add base network * add word similarity to search * fix word similarity test case * add defaultMode to replication * Continue on integrating with base chain * Fix migrations * Fix migrations * optimize projectById * add passport score to streams minidonations * Add methods for adding matching to totals usd received (#1582) * add methods for adding matching to totals usd received * Update src/repositories/qfRoundHistoryRepository.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: mohammadranjbarz Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * add coalesce to get 0 always incase null total matching * Add test cases about filteriing base network * Add BASE node http urls for rpc * Add BASE node http urls for rpc * Add missing things for integrating with base * merge stage * Limit characters of each cell to less than 5000 characters * Fix eslint errors * Fix creating new token with coingeckoId related to https://github.com/Giveth/impact-graph/issues/1564 * Fill base mainnet tokens related to https://github.com/Giveth/impact-graph/issues/1561 * optimize qfRounds query * Add base chain (#1579) * Ignore small differences of amounts when matching draft donation for … (#1573) * Ignore small differences of amounts when matching draft donation for erc20 * Fix eslint errors * Release Integrate QF with super fluid streamed donations (#1555) * add activeQfRoundId to sortingBy InstantBoosting * add orderBy totalDonations and totalReactions * feat: add getRecurringDonationStats resolver * fix filtering by QF * remove qfRounds joins for non qf round filters * add some temp logs * remove temp logs * fix: changes test cases of recuring donations stats - create recored with createdAt field in past so test result won't be related to other endpoints test cases * Fix projectActualserviceView * fix stream balance depleted issue (#1496) Co-authored-by: mohammadranjbarz * rebuild * refresh and fetch user address separately (#1499) * Added pg_trgm extension migration (#1502) * fix: change recurring donations stats query to single query * fix recurring donation count * WIP: projectIds textArea * fix actual matching cap (#1507) * fix query error * fix user donations count * fix recurring donation count tests * fix user recurring donation query * fix user recurring donation test * add donations relation to qfround * add findArchivedQfRounds endpoint * add findArchivedQfRounds endpoint * feat: add sponsors & banner images upload * add sortBy to findArchivedQfRounds * 1.23.3 * add new test graphql query * add tests for new QfArchivedRounds * fixes on qfArchivedRounds query * add new tests for qfArchivedRounds query * fix findArchivedQfRounds tests * fix: keep already uploaded sponsors images * fix skip and limit for findArchivedQfRounds * Add logs and refactor the bootstrap code to help investigate latency problem * Add poolSize to orm config * Fix eslint errors * remove changing squareRootSumOfProjects when cap is overflown * Trigger ortto activity when user saves their profile info for the first time (#1520) * add newUser to updateUser query * add createOrttoProfile * add createOrttoProfile to NotificationAdapterInterface * add createOrttoProfile to MockNotificationAdapter * add CREATE_ORTTO_PROFILE event * Allow to set the matching pool token & amount to be something other than usd (#1517) * Allow to set the matching pool token & amount to be something other than USD in adminjs * Allow to set the matching pool token & amount to be something other than USD in QFRound table * add null to allocatedTokenSymbol and allocatedTokenChainId * add nullable true to allocatedTokenSymbol and allocatedTokenChainId * add allocatedFundUSDPreferred and allocatedFundUSD to qfRound * Comment migrations * Hotfix db improvements (#1523) * add extra configurations for postgresql connections * add master and slave replication strategy for typeorm * add qfRound to qfRoundStats * fix qfRoundStatsQuery * Hotfix staging fix latency (#1528) * add project donation summary view entity * convert projectQueries to querybuilder * add cache to projectDonationSummary queries * add configurable cache to slow queries * remove massive recurring donation log * add await for project queries * Add logs to projectVerificationForm * Add logs to projectVerificationForm * fix: add project Ids list textarea for qf round edit * Master to staging (#1543) * Hotfix db improvements (#1523) (#1524) * add extra configurations for postgresql connections * add master and slave replication strategy for typeorm Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> * Fix/db replica production (#1525) * Hotfix db improvements (#1523) * add extra configurations for postgresql connections * add master and slave replication strategy for typeorm * Define db read only configs --------- Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> * update comment * Hotfix latency issues for prod (#1529) * Hotfix staging fix latency (#1528) * add project donation summary view entity * convert projectQueries to querybuilder * add cache to projectDonationSummary queries * add configurable cache to slow queries * remove massive recurring donation log * add await for project queries * Add informative logs for draft donation service job (#1537) * Fix eslint errors * Fix/master test (#1541) * Fixed master test issue * Returned test to master pipeline * Comment executing donation summary view --------- Co-authored-by: Mohammad Ranjbar Z --------- Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> Co-authored-by: Carlos Co-authored-by: mohammadranjbarz * Fix/word similarity - staging (#1546) * Hotfix db improvements (#1523) (#1524) * add extra configurations for postgresql connections * add master and slave replication strategy for typeorm Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> * Fix/db replica production (#1525) * Hotfix db improvements (#1523) * add extra configurations for postgresql connections * add master and slave replication strategy for typeorm * Define db read only configs --------- Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> * update comment * Hotfix latency issues for prod (#1529) * Hotfix staging fix latency (#1528) * add project donation summary view entity * convert projectQueries to querybuilder * add cache to projectDonationSummary queries * add configurable cache to slow queries * remove massive recurring donation log * add await for project queries * Add informative logs for draft donation service job (#1537) * Fix eslint errors * Fix/master test (#1541) * Fixed master test issue * Returned test to master pipeline * Comment executing donation summary view --------- Co-authored-by: Mohammad Ranjbar Z * Fixed word similarity issue * Removed unused import --------- Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> Co-authored-by: Carlos Co-authored-by: mohammadranjbarz * remove refresh_project_summary_from totals and add user cache (#1539) * Remove users field from project * Remove users field from filterProjectsQuery * Remove projects field from user * remove added logs * fix eslint errors * remove users from Project.create * remove users from Project.create in testUtils.ts * remove projectOwnerId * replace admin with adminUserId * Add recurring donation join to donations() endpoint (#1554) related to #1483 * replace admin with adminUserId in SEED data * replace admin with adminUserId * replace admin with adminUserId in projectResolver.ts * replace admin with adminUserId in projectsTab.ts * replace admin with adminUserId in projectResolver.test.ts * replace admin with adminUserId in projectResolver.ts * add allocatedFundUSD and allocatedTokenSymbol to qfArchivedRounds * fix nullable * remove admin from project * replace admin with adminUserId * replace admin with adminUserId * add adminUserId field * drop admin column * fix: add telegram to ProjectSocialMediaType enum to allow adding telegram url * Add some logs * Fix eslint errors * Add maxQueuedJobs for draft donation worker * Fix eslint errors * fix unstable test case * Disable concurrency for draft donation worker * add indexes to project_summary_view (#1568) * improve projectBySlug query * fix: change output types to float * Ignore small differences of amounts when matching draft donation for erc20 * add graphql-fields * add getVerificationFormStatusByProjectId * refactor projectBySlug * add findProjectIdBySlug * fix projectBySlug tests with new changes * fix projectBySlug tests with new changes * fix projectBySlug tests with new changes * remove projectVerificationForm assert * add logs * fix title in should return projects with indicated slug test * Add streamed mini donations to qf round (#1557) * Add streamed mini donations to qf round related to Giveth/giveth-dapps-v2#3284 * Fix eslint error * Fix eslint errors * make verificationFormStatus field public * Reduce test runnning time (#1574) * fix: reduce test runnning time * fix: add permissions test case * fix: add permissions test case * fix: refactor (excluding permissions test cases from global beforeEach) --------- Co-authored-by: Ramin Co-authored-by: Meriem-BM Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> Co-authored-by: Amin Latifi Co-authored-by: Carlos * WIP add base network * Continue on integrating with base chain * Fix migrations * Fix migrations * Add test cases about filteriing base network * Add BASE node http urls for rpc * Add BASE node http urls for rpc * Add missing things for integrating with base * Fix creating new token with coingeckoId related to https://github.com/Giveth/impact-graph/issues/1564 * Fill base mainnet tokens related to https://github.com/Giveth/impact-graph/issues/1561 --------- Co-authored-by: Ramin Co-authored-by: Meriem-BM Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> Co-authored-by: Amin Latifi Co-authored-by: Carlos * Add coingeckoId to BASE_SEPOLIA (#1592) * Ignore small differences of amounts when matching draft donation for … (#1573) * Ignore small differences of amounts when matching draft donation for erc20 * Fix eslint errors * Release Integrate QF with super fluid streamed donations (#1555) * add activeQfRoundId to sortingBy InstantBoosting * add orderBy totalDonations and totalReactions * feat: add getRecurringDonationStats resolver * fix filtering by QF * remove qfRounds joins for non qf round filters * add some temp logs * remove temp logs * fix: changes test cases of recuring donations stats - create recored with createdAt field in past so test result won't be related to other endpoints test cases * Fix projectActualserviceView * fix stream balance depleted issue (#1496) Co-authored-by: mohammadranjbarz * rebuild * refresh and fetch user address separately (#1499) * Added pg_trgm extension migration (#1502) * fix: change recurring donations stats query to single query * fix recurring donation count * WIP: projectIds textArea * fix actual matching cap (#1507) * fix query error * fix user donations count * fix recurring donation count tests * fix user recurring donation query * fix user recurring donation test * add donations relation to qfround * add findArchivedQfRounds endpoint * add findArchivedQfRounds endpoint * feat: add sponsors & banner images upload * add sortBy to findArchivedQfRounds * 1.23.3 * add new test graphql query * add tests for new QfArchivedRounds * fixes on qfArchivedRounds query * add new tests for qfArchivedRounds query * fix findArchivedQfRounds tests * fix: keep already uploaded sponsors images * fix skip and limit for findArchivedQfRounds * Add logs and refactor the bootstrap code to help investigate latency problem * Add poolSize to orm config * Fix eslint errors * remove changing squareRootSumOfProjects when cap is overflown * Trigger ortto activity when user saves their profile info for the first time (#1520) * add newUser to updateUser query * add createOrttoProfile * add createOrttoProfile to NotificationAdapterInterface * add createOrttoProfile to MockNotificationAdapter * add CREATE_ORTTO_PROFILE event * Allow to set the matching pool token & amount to be something other than usd (#1517) * Allow to set the matching pool token & amount to be something other than USD in adminjs * Allow to set the matching pool token & amount to be something other than USD in QFRound table * add null to allocatedTokenSymbol and allocatedTokenChainId * add nullable true to allocatedTokenSymbol and allocatedTokenChainId * add allocatedFundUSDPreferred and allocatedFundUSD to qfRound * Comment migrations * Hotfix db improvements (#1523) * add extra configurations for postgresql connections * add master and slave replication strategy for typeorm * add qfRound to qfRoundStats * fix qfRoundStatsQuery * Hotfix staging fix latency (#1528) * add project donation summary view entity * convert projectQueries to querybuilder * add cache to projectDonationSummary queries * add configurable cache to slow queries * remove massive recurring donation log * add await for project queries * Add logs to projectVerificationForm * Add logs to projectVerificationForm * fix: add project Ids list textarea for qf round edit * Master to staging (#1543) * Hotfix db improvements (#1523) (#1524) * add extra configurations for postgresql connections * add master and slave replication strategy for typeorm Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> * Fix/db replica production (#1525) * Hotfix db improvements (#1523) * add extra configurations for postgresql connections * add master and slave replication strategy for typeorm * Define db read only configs --------- Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> * update comment * Hotfix latency issues for prod (#1529) * Hotfix staging fix latency (#1528) * add project donation summary view entity * convert projectQueries to querybuilder * add cache to projectDonationSummary queries * add configurable cache to slow queries * remove massive recurring donation log * add await for project queries * Add informative logs for draft donation service job (#1537) * Fix eslint errors * Fix/master test (#1541) * Fixed master test issue * Returned test to master pipeline * Comment executing donation summary view --------- Co-authored-by: Mohammad Ranjbar Z --------- Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> Co-authored-by: Carlos Co-authored-by: mohammadranjbarz * Fix/word similarity - staging (#1546) * Hotfix db improvements (#1523) (#1524) * add extra configurations for postgresql connections * add master and slave replication strategy for typeorm Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> * Fix/db replica production (#1525) * Hotfix db improvements (#1523) * add extra configurations for postgresql connections * add master and slave replication strategy for typeorm * Define db read only configs --------- Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> * update comment * Hotfix latency issues for prod (#1529) * Hotfix staging fix latency (#1528) * add project donation summary view entity * convert projectQueries to querybuilder * add cache to projectDonationSummary queries * add configurable cache to slow queries * remove massive recurring donation log * add await for project queries * Add informative logs for draft donation service job (#1537) * Fix eslint errors * Fix/master test (#1541) * Fixed master test issue * Returned test to master pipeline * Comment executing donation summary view --------- Co-authored-by: Mohammad Ranjbar Z * Fixed word similarity issue * Removed unused import --------- Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> Co-authored-by: Carlos Co-authored-by: mohammadranjbarz * remove refresh_project_summary_from totals and add user cache (#1539) * Remove users field from project * Remove users field from filterProjectsQuery * Remove projects field from user * remove added logs * fix eslint errors * remove users from Project.create * remove users from Project.create in testUtils.ts * remove projectOwnerId * replace admin with adminUserId * Add recurring donation join to donations() endpoint (#1554) related to #1483 * replace admin with adminUserId in SEED data * replace admin with adminUserId * replace admin with adminUserId in projectResolver.ts * replace admin with adminUserId in projectsTab.ts * replace admin with adminUserId in projectResolver.test.ts * replace admin with adminUserId in projectResolver.ts * add allocatedFundUSD and allocatedTokenSymbol to qfArchivedRounds * fix nullable * remove admin from project * replace admin with adminUserId * replace admin with adminUserId * add adminUserId field * drop admin column * fix: add telegram to ProjectSocialMediaType enum to allow adding telegram url * Add some logs * Fix eslint errors * Add maxQueuedJobs for draft donation worker * Fix eslint errors * fix unstable test case * Disable concurrency for draft donation worker * add indexes to project_summary_view (#1568) * improve projectBySlug query * fix: change output types to float * Ignore small differences of amounts when matching draft donation for erc20 * add graphql-fields * add getVerificationFormStatusByProjectId * refactor projectBySlug * add findProjectIdBySlug * fix projectBySlug tests with new changes * fix projectBySlug tests with new changes * fix projectBySlug tests with new changes * remove projectVerificationForm assert * add logs * fix title in should return projects with indicated slug test * Add streamed mini donations to qf round (#1557) * Add streamed mini donations to qf round related to Giveth/giveth-dapps-v2#3284 * Fix eslint error * Fix eslint errors * make verificationFormStatus field public * Reduce test runnning time (#1574) * fix: reduce test runnning time * fix: add permissions test case * fix: add permissions test case * fix: refactor (excluding permissions test cases from global beforeEach) --------- Co-authored-by: Ramin Co-authored-by: Meriem-BM Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> Co-authored-by: Amin Latifi Co-authored-by: Carlos * WIP add base network * Continue on integrating with base chain * Fix migrations * Fix migrations * Add test cases about filteriing base network * Add BASE node http urls for rpc * Add BASE node http urls for rpc * Add missing things for integrating with base * Fix creating new token with coingeckoId related to https://github.com/Giveth/impact-graph/issues/1564 * Fill base mainnet tokens related to https://github.com/Giveth/impact-graph/issues/1561 * add coingeckoId to BASE_SEPOLIA --------- Co-authored-by: mohammadranjbarz Co-authored-by: Ramin Co-authored-by: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> Co-authored-by: Amin Latifi Co-authored-by: Carlos Co-authored-by: Meriem-B <135605616+Meriem-B@users.noreply.github.com> * Deleting redis * sync all donations totals (#1594) * sync all donations totals * add update qfroundmatching * Add coingeckoId for BASE ETH * Changee isGivbaclEligble of base chain tokens to false --------- Co-authored-by: Ramin Co-authored-by: Meriem-BM Co-authored-by: Mohammad Ranjbar Z Co-authored-by: Amin Latifi Co-authored-by: Rolazo Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Meriem-B <135605616+Meriem-B@users.noreply.github.com> Co-authored-by: Nicolas <38561963+Rolazo@users.noreply.github.com> --- .github/workflows/develop-pipeline.yml | 2 + .github/workflows/master-pipeline.yml | 2 + .github/workflows/staging-pipeline.yml | 2 + config/example.env | 11 + config/test.env | 6 + docker-compose-local-postgres-db-readonly.yml | 44 +++ .../1716367359560-add_base_chain_tokens.ts | 55 ++++ migration/data/seedTokens.ts | 288 ++++++++++++++++++ package.json | 4 +- src/entities/project.ts | 1 + src/orm.ts | 1 + src/provider.ts | 50 +++ src/repositories/qfRoundRepository.ts | 28 +- src/resolvers/donationResolver.test.ts | 2 +- .../projectResolver.allProject.test.ts | 107 ++++++- src/resolvers/projectResolver.test.ts | 1 - src/resolvers/projectResolver.ts | 79 ++++- .../projectVerificationFormResolver.test.ts | 10 + src/resolvers/qfRoundResolver.ts | 17 +- src/server/adminJs/tabs/donationTab.ts | 2 + src/server/adminJs/tabs/qfRoundTab.ts | 2 + src/server/adminJs/tabs/tokenTab.ts | 4 + src/services/chains/index.test.ts | 38 +++ src/services/googleSheets.ts | 20 +- src/utils/networksConfig.ts | 2 + .../validators/graphqlQueryValidators.ts | 2 + src/utils/validators/projectValidator.ts | 2 + test/graphqlQueries.ts | 1 - test/pre-test-scripts.ts | 28 ++ test/testUtils.ts | 18 ++ 30 files changed, 797 insertions(+), 32 deletions(-) create mode 100644 docker-compose-local-postgres-db-readonly.yml create mode 100644 migration/1716367359560-add_base_chain_tokens.ts diff --git a/.github/workflows/develop-pipeline.yml b/.github/workflows/develop-pipeline.yml index bf6209ae8..58a3f5af2 100644 --- a/.github/workflows/develop-pipeline.yml +++ b/.github/workflows/develop-pipeline.yml @@ -65,6 +65,8 @@ jobs: CELO_ALFAJORES_SCAN_API_KEY: ${{ secrets.CELO_ALFAJORES_SCAN_API_KEY }} ARBITRUM_SCAN_API_KEY: ${{ secrets.ARBITRUM_SCAN_API_KEY }} ARBITRUM_SEPOLIA_SCAN_API_KEY: ${{ secrets.ARBITRUM_SEPOLIA_SCAN_API_KEY }} + BASE_SCAN_API_KEY: ${{ secrets.BASE_SCAN_API_KEY }} + BASE_SEPOLIA_SCAN_API_KEY: ${{ secrets.BASE_SEPOLIA_SCAN_API_KEY }} MORDOR_ETC_TESTNET: ${{ secrets.MORDOR_ETC_TESTNET }} ETC_NODE_HTTP_URL: ${{ secrets.ETC_NODE_HTTP_URL }} SOLANA_TEST_NODE_RPC_URL: ${{ secrets.SOLANA_TEST_NODE_RPC_URL }} diff --git a/.github/workflows/master-pipeline.yml b/.github/workflows/master-pipeline.yml index 6bcebcb83..433ab725d 100644 --- a/.github/workflows/master-pipeline.yml +++ b/.github/workflows/master-pipeline.yml @@ -102,6 +102,8 @@ jobs: CELO_ALFAJORES_SCAN_API_KEY: ${{ secrets.CELO_ALFAJORES_SCAN_API_KEY }} ARBITRUM_SCAN_API_KEY: ${{ secrets.ARBITRUM_SCAN_API_KEY }} ARBITRUM_SEPOLIA_SCAN_API_KEY: ${{ secrets.ARBITRUM_SEPOLIA_SCAN_API_KEY }} + BASE_SCAN_API_KEY: ${{ secrets.BASE_SCAN_API_KEY }} + BASE_SEPOLIA_SCAN_API_KEY: ${{ secrets.BASE_SEPOLIA_SCAN_API_KEY }} MORDOR_ETC_TESTNET: ${{ secrets.MORDOR_ETC_TESTNET }} ETC_NODE_HTTP_URL: ${{ secrets.ETC_NODE_HTTP_URL }} DROP_DATABASE: ${{ secrets.DROP_DATABASE_DURING_TEST_PROD }} diff --git a/.github/workflows/staging-pipeline.yml b/.github/workflows/staging-pipeline.yml index 388f4660c..b49337e84 100644 --- a/.github/workflows/staging-pipeline.yml +++ b/.github/workflows/staging-pipeline.yml @@ -102,6 +102,8 @@ jobs: CELO_ALFAJORES_SCAN_API_KEY: ${{ secrets.CELO_ALFAJORES_SCAN_API_KEY }} ARBITRUM_SCAN_API_KEY: ${{ secrets.ARBITRUM_SCAN_API_KEY }} ARBITRUM_SEPOLIA_SCAN_API_KEY: ${{ secrets.ARBITRUM_SEPOLIA_SCAN_API_KEY }} + BASE_SCAN_API_KEY: ${{ secrets.BASE_SCAN_API_KEY }} + BASE_SEPOLIA_SCAN_API_KEY: ${{ secrets.BASE_SEPOLIA_SCAN_API_KEY }} MORDOR_ETC_TESTNET: ${{ secrets.MORDOR_ETC_TESTNET }} ETC_NODE_HTTP_URL: ${{ secrets.ETC_NODE_HTTP_URL }} DROP_DATABASE: ${{ secrets.DROP_DATABASE_DURING_TEST_STAGING }} diff --git a/config/example.env b/config/example.env index 8c3393325..18fb2d657 100644 --- a/config/example.env +++ b/config/example.env @@ -307,3 +307,14 @@ ENABLE_DRAFT_RECURRING_DONATION=true DRAFT_RECURRING_DONATION_MATCH_EXPIRATION_HOURS=24 OPTIMISTIC_SEPOLIA_SCAN_API_KEY= + +BASE_SCAN_API_URL=https://api.basescan.org/api +BASE_SCAN_API_KEY=0000000000000000000000000000000000 +BASE_SEPOLIA_SCAN_API_URL=https://api-sepolia.basescan.org/api +BASE_SEPOLIA_SCAN_API_KEY=0000000000000000000000000000000000 + +# BASE MAINNET +BASE_MAINNET_NODE_HTTP_URL= + +# BASE SEPOLIA +BASE_SEPOLIA_NODE_HTTP_URL= diff --git a/config/test.env b/config/test.env index ef55172b7..6092dca7c 100644 --- a/config/test.env +++ b/config/test.env @@ -47,6 +47,12 @@ ARBITRUM_SCAN_API_URL=https://api.arbiscan.io/api ARBITRUM_SCAN_API_KEY=0000000000000000000000000000000000 ARBITRUM_SEPOLIA_SCAN_API_URL=https://api-sepolia.arbiscan.io/api ARBITRUM_SEPOLIA_SCAN_API_KEY=0000000000000000000000000000000000 + +BASE_SCAN_API_URL=https://api.basescan.org/api +BASE_SCAN_API_KEY=0000000000000000000000000000000000 +BASE_SEPOLIA_SCAN_API_URL=https://api-sepolia.basescan.org/api +BASE_SEPOLIA_SCAN_API_KEY=0000000000000000000000000000000000 + GNOSISSCAN_API_URL=https://api.gnosisscan.io/api ETHERSCAN_API_KEY=00000000000000000000000000000000 GNOSISSCAN_API_KEY=0000000000000000000000000000000000 diff --git a/docker-compose-local-postgres-db-readonly.yml b/docker-compose-local-postgres-db-readonly.yml new file mode 100644 index 000000000..3e56f8800 --- /dev/null +++ b/docker-compose-local-postgres-db-readonly.yml @@ -0,0 +1,44 @@ +services: + impact-graph-postgres: + # Use this postgres image https://github.com/Giveth/postgres-givethio + image: ghcr.io/giveth/postgres-givethio:latest + restart: always + environment: + - POSTGRES_DB=givethio + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + - PGDATA=/var/lib/postgresql/data/pgdata + ports: + - "5432:5432" + volumes: + - db-data:/var/lib/postgresql/data + + impact-graph-postgres-replica: + # Read-only replica of the main Postgres container + image: ghcr.io/giveth/postgres-givethio:latest + restart: always + environment: + - POSTGRES_DB=givethio + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + - PGDATA=/var/lib/postgresql/data/pgdata + - POSTGRES_PRIMARY_HOST=impact-graph-postgres + - POSTGRES_PRIMARY_PORT=5432 + - POSTGRES_PRIMARY_USER=postgres + - POSTGRES_PRIMARY_PASSWORD=postgres + - POSTGRES_REPLICA=true + ports: + - "5431:5432" + volumes: + - db-data-replica:/var/lib/postgresql/data + networks: + - giveth + + +volumes: + db-data: + db-data-replica: + + +networks: + giveth: \ No newline at end of file diff --git a/migration/1716367359560-add_base_chain_tokens.ts b/migration/1716367359560-add_base_chain_tokens.ts new file mode 100644 index 000000000..4e5f0eb00 --- /dev/null +++ b/migration/1716367359560-add_base_chain_tokens.ts @@ -0,0 +1,55 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; +import { Token } from '../src/entities/token'; +import seedTokens from './data/seedTokens'; +import config from '../src/config'; +import { NETWORK_IDS } from '../src/provider'; + +export class AddBaseChainTokens1716367359560 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + const environment = config.get('ENVIRONMENT') as string; + + const networkId = + environment === 'production' + ? NETWORK_IDS.BASE_MAINNET + : NETWORK_IDS.BASE_SEPOLIA; + + await queryRunner.manager.save( + Token, + seedTokens + .filter(token => token.networkId === networkId) + .map(token => { + const t = { + ...token, + }; + t.address = t.address?.toLowerCase(); + delete t.chainType; + return t; + }), + ); + const tokens = await queryRunner.query(` + SELECT * FROM token + WHERE "networkId" = ${networkId} + `); + const givethOrganization = ( + await queryRunner.query(`SELECT * FROM organization + WHERE label='giveth'`) + )[0]; + + const traceOrganization = ( + await queryRunner.query(`SELECT * FROM organization + WHERE label='trace'`) + )[0]; + + for (const token of tokens) { + // Add all Base tokens to Giveth organization + await queryRunner.query(`INSERT INTO organization_tokens_token ("tokenId","organizationId") VALUES + (${token.id}, ${givethOrganization.id}), + (${token.id}, ${traceOrganization.id}) + ;`); + } + } + + public async down(_queryRunner: QueryRunner): Promise { + // + } +} diff --git a/migration/data/seedTokens.ts b/migration/data/seedTokens.ts index be4fe6dec..f9193b370 100644 --- a/migration/data/seedTokens.ts +++ b/migration/data/seedTokens.ts @@ -15,6 +15,7 @@ interface ITokenData { coingeckoId?: string; isStableCoin?: boolean; } + const seedTokens: ITokenData[] = [ // Mainnet tokens { @@ -1593,6 +1594,293 @@ const seedTokens: ITokenData[] = [ networkId: NETWORK_IDS.ARBITRUM_MAINNET, coingeckoId: 'cartesi', }, + + // BASE Sepolia + { + name: 'BASE', + symbol: 'ETH', + address: '0x0000000000000000000000000000000000000000', + decimals: 18, + networkId: NETWORK_IDS.BASE_SEPOLIA, + coingeckoId: 'ethereum', + }, + + // BASE Mainnet - https://basescan.org/token/0x0000000000000000000000000000000000000000 + { + name: 'BASE', + symbol: 'ETH', + address: '0x0000000000000000000000000000000000000000', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'ethereum', + isGivbackEligible: false, + }, + + // USDC - https://basescan.org/token/0x833589fcd6edb6e08f4c7c32d4f71b54bda02913 + { + name: 'USDC', + symbol: 'USDC', + address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', + decimals: 6, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'usd-coin', + isGivbackEligible: false, + isStableCoin: true, + }, + // Dai Stablecoin - https://basescan.org/token/0x50c5725949a6f0c72e6c4a641f24049a917db0cb + { + name: 'Dai Stablecoin', + symbol: 'DAI', + address: '0x50c5725949a6f0c72e6c4a641f24049a917db0cb', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'dai', + isGivbackEligible: false, + isStableCoin: true, + }, + // Rocket Pool ETH - https://basescan.org/token/0xb6fe221fe9eef5aba221c348ba20a1bf5e73624c + { + name: 'Rocket Pool ETH', + symbol: 'rETH', + address: '0xb6fe221fe9eef5aba221c348ba20a1bf5e73624c', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'rocket-pool-eth', + isGivbackEligible: false, + }, + // Synthetix Network Token - https://basescan.org/token/0x22e6966b799c4d5b13be962e1d117b56327fda66 + { + name: 'Synthetix Network Token', + symbol: 'SNX', + address: '0x22e6966b799c4d5b13be962e1d117b56327fda66', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'havven', + isGivbackEligible: false, + }, + // Coinbase Wrapped Staked ETH - https://basescan.org/token/0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22 + { + name: 'Coinbase Wrapped Staked ETH', + symbol: 'cbETH', + address: '0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'coinbase-wrapped-staked-eth', + isGivbackEligible: false, + }, + // Prime - https://basescan.org/token/0xfA980cEd6895AC314E7dE34Ef1bFAE90a5AdD21b + { + name: 'Prime', + symbol: 'PRIME', + address: '0xfA980cEd6895AC314E7dE34Ef1bFAE90a5AdD21b', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'echelon-prime', + isGivbackEligible: false, + }, + // Aerodrome - https://basescan.org/token/0x940181a94a35a4569e4529a3cdfb74e38fd98631 + { + name: 'Aerodrome', + symbol: 'AERO', + address: '0x940181a94a35a4569e4529a3cdfb74e38fd98631', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'aerodrome-finance', + isGivbackEligible: false, + }, + // Degen - https://basescan.org/token/0x4ed4e862860bed51a9570b96d89af5e1b0efefed + { + name: 'Degen', + symbol: 'DEGEN', + address: '0x4ed4e862860bed51a9570b96d89af5e1b0efefed', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'degen-base', + isGivbackEligible: false, + }, + // Osaka Protocol - https://basescan.org/token/0xbFd5206962267c7b4b4A8B3D76AC2E1b2A5c4d5e + { + name: 'Osaka Protocol', + symbol: 'OSAK', + address: '0xbFd5206962267c7b4b4A8B3D76AC2E1b2A5c4d5e', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'osaka-protocol', + isGivbackEligible: false, + }, + // BTRST - https://basescan.org/token/0xa7d68d155d17cb30e311367c2ef1e82ab6022b67 + { + name: 'BTRST', + symbol: 'BTRST', + address: '0xa7d68d155d17cb30e311367c2ef1e82ab6022b67', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'braintrust', + isGivbackEligible: false, + }, + // SmarDex Token - https://basescan.org/token/0xfd4330b0312fdeec6d4225075b82e00493ff2e3f + { + name: 'SmarDex Token', + symbol: 'SDEX', + address: '0xfd4330b0312fdeec6d4225075b82e00493ff2e3f', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'smardex', + isGivbackEligible: false, + }, + // Spectral Token - https://basescan.org/token/0x96419929d7949d6a801a6909c145c8eef6a40431 + { + name: 'Spectral Token', + symbol: 'SPEC', + address: '0x96419929d7949d6a801a6909c145c8eef6a40431', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'spectral', + isGivbackEligible: false, + }, + // Heroes of Mavia - https://basescan.org/token/0x24fcFC492C1393274B6bcd568ac9e225BEc93584 + { + name: 'Heroes of Mavia', + symbol: 'MAVIA', + address: '0x24fcFC492C1393274B6bcd568ac9e225BEc93584', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'heroes-of-mavia', + isGivbackEligible: false, + }, + // Maverick Token - https://basescan.org/token/0x64b88c73a5dfa78d1713fe1b4c69a22d7e0faaa7 + { + name: 'Maverick Token', + symbol: 'MAV', + address: '0x64b88c73a5dfa78d1713fe1b4c69a22d7e0faaa7', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'maverick-protocol', + isGivbackEligible: false, + }, + // Dola USD Stablecoin - https://basescan.org/token/0x4621b7a9c75199271f773ebd9a499dbd165c3191 + { + name: 'Dola USD Stablecoin', + symbol: 'DOLA', + address: '0x4621b7a9c75199271f773ebd9a499dbd165c3191', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'dola-usd', + isGivbackEligible: false, + isStableCoin: true, + }, + // USD+ - https://basescan.org/token/0xb79dd08ea68a908a97220c76d19a6aa9cbde4376 + { + name: 'USD+', + symbol: 'USD+', + address: '0xb79dd08ea68a908a97220c76d19a6aa9cbde4376', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'usd', + isGivbackEligible: false, + isStableCoin: true, + }, + // Magic Internet Money - https://basescan.org/token/0x4A3A6Dd60A34bB2Aba60D73B4C88315E9CeB6A3D + { + name: 'Magic Internet Money', + symbol: 'MIM', + address: '0x4A3A6Dd60A34bB2Aba60D73B4C88315E9CeB6A3D', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'magic-internet-money', + isGivbackEligible: false, + }, + // Seamless - https://basescan.org/token/0x1c7a460413dd4e964f96d8dfc56e7223ce88cd85 + { + name: 'Seamless', + symbol: 'SEAM', + address: '0x1c7a460413dd4e964f96d8dfc56e7223ce88cd85', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'seamless-protocol', + isGivbackEligible: false, + }, + // Extra Finance - https://basescan.org/token/0x2dad3a13ef0c6366220f989157009e501e7938f8 + { + name: 'Extra Finance', + symbol: 'EXTRA', + address: '0x2dad3a13ef0c6366220f989157009e501e7938f8', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'extra-finance', + isGivbackEligible: false, + }, + // agEUR - https://basescan.org/token/0xa61beb4a3d02decb01039e378237032b351125b4 + { + name: 'agEUR', + symbol: 'agEUR', + address: '0xa61beb4a3d02decb01039e378237032b351125b4', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'ageur-plenty-bridge', + isGivbackEligible: false, + isStableCoin: true, + }, + // SubQueryToken - https://basescan.org/token/0x858c50C3AF1913b0E849aFDB74617388a1a5340d + { + name: 'SubQueryToken', + symbol: 'SQT', + address: '0x858c50C3AF1913b0E849aFDB74617388a1a5340d', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'subquery-network', + isGivbackEligible: false, + }, + // Rai.Finance - https://basescan.org/token/0x703d57164ca270b0b330a87fd159cfef1490c0a5 + { + name: 'Rai.Finance', + symbol: 'SOFI', + address: '0x703d57164ca270b0b330a87fd159cfef1490c0a5', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'rai-finance', + isGivbackEligible: false, + }, + // UNJD (MBS) - https://basescan.org/token/0x8fbd0648971d56f1f2c35fa075ff5bc75fb0e39d + { + name: 'UNJD', + symbol: 'MBS', + address: '0x8fbd0648971d56f1f2c35fa075ff5bc75fb0e39d', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'monkeyball', + isGivbackEligible: false, + }, + // Rigo Token - https://basescan.org/token/0x09188484e1ab980daef53a9755241d759c5b7d60 + { + name: 'Rigo Token', + symbol: 'GRG', + address: '0x09188484e1ab980daef53a9755241d759c5b7d60', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'rigoblock', + isGivbackEligible: false, + }, + // DEUS - https://basescan.org/token/0xde5ed76e7c05ec5e4572cfc88d1acea165109e44 + { + name: 'DEUS', + symbol: 'DEUS', + address: '0xde5ed76e7c05ec5e4572cfc88d1acea165109e44', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'deus-finance-2', + isGivbackEligible: false, + }, + // Alongside Crypto Market Index - https://basescan.org/token/0x13f4196cc779275888440b3000ae533bbbbc3166 + { + name: 'Alongside Crypto Market Index', + symbol: 'AMKT', + address: '0x13f4196cc779275888440b3000ae533bbbbc3166', + decimals: 18, + networkId: NETWORK_IDS.BASE_MAINNET, + coingeckoId: 'alongside-crypto-market-index', + isGivbackEligible: false, + }, ]; export default seedTokens; diff --git a/package.json b/package.json index 4174475a4..76f307419 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "test:reactionResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/reactionResolver.test.ts", "test:donationResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/donationResolver.test.ts", "test:draftDonationResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/draftDonationResolver.test.ts", - "test:projectResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/projectResolver.test.ts", + "test:projectResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/projectResolver.test.ts ./src/resolvers/projectResolver.allProject.test.ts", "test:chainvineResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/chainvineResolver.test.ts", "test:qfRoundResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/qfRoundResolver.test.ts", "test:qfRoundHistoryResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/qfRoundHistoryResolver.test.ts", @@ -192,7 +192,7 @@ "test:instantPowerBoostingService": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/instantBoostingServices.test.ts", "test:actualMatchingFundView": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/actualMatchingFundView.test.ts", "test:categoryResolver": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/resolvers/categoryResolver.test.ts", - "test:givpower": "NODE_ENV=test mocha -b -t 30000 ./test/pre-test-scripts.ts ./src/repositories/powerBoostingRepository.test.ts ./src/repositories/userPowerRepository.test.ts ./src/repositories/powerRoundRepository.test.ts ./src/repositories/userProjectPowerViewRepository.test.ts ./src/repositories/projectPowerViewRepository.test.ts ./src/resolvers/powerBoostingResolver.test.ts ./src/resolvers/userProjectPowerResolver.test.ts ./src/resolvers/projectPowerResolver.test.ts ./src/adapters/givpowerSubgraph/givPowerSubgraphAdapter.test.ts ./src/repositories/projectRepository.test.ts ./src/resolvers/projectResolver.test.ts ./src/repositories/dbCronRepository.test.ts", + "test:givpower": "NODE_ENV=test mocha -b -t 30000 ./test/pre-test-scripts.ts ./src/repositories/powerBoostingRepository.test.ts ./src/repositories/userPowerRepository.test.ts ./src/repositories/powerRoundRepository.test.ts ./src/repositories/userProjectPowerViewRepository.test.ts ./src/repositories/projectPowerViewRepository.test.ts ./src/resolvers/powerBoostingResolver.test.ts ./src/resolvers/userProjectPowerResolver.test.ts ./src/resolvers/projectPowerResolver.test.ts ./src/adapters/givpowerSubgraph/givPowerSubgraphAdapter.test.ts ./src/repositories/projectRepository.test.ts ./src/resolvers/projectResolver.test.ts ./src/resolvers/projectResolver.allProject.test.ts ./src/repositories/dbCronRepository.test.ts", "test:apiGive": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/routers/apiGivRoutes.test.ts", "test:adminJs": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/server/adminJs/**/*.test.ts ", "test:adminJsRolePermissions": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/server/adminJs/adminJsPermissions.test.ts", diff --git a/src/entities/project.ts b/src/entities/project.ts index 479a12979..f5bfbdb0d 100644 --- a/src/entities/project.ts +++ b/src/entities/project.ts @@ -86,6 +86,7 @@ export enum FilterField { AcceptFundOnETC = 'acceptFundOnETC', AcceptFundOnCelo = 'acceptFundOnCelo', AcceptFundOnArbitrum = 'acceptFundOnArbitrum', + AcceptFundOnBase = 'acceptFundOnBase', AcceptFundOnOptimism = 'acceptFundOnOptimism', AcceptFundOnSolana = 'acceptFundOnSolana', GivingBlock = 'fromGivingBlock', diff --git a/src/orm.ts b/src/orm.ts index e1f399452..7961e434e 100644 --- a/src/orm.ts +++ b/src/orm.ts @@ -29,6 +29,7 @@ export class AppDataSource { schema: 'public', type: 'postgres', replication: { + defaultMode: 'master', master: { database: config.get('TYPEORM_DATABASE_NAME') as string, username: config.get('TYPEORM_DATABASE_USER') as string, diff --git a/src/provider.ts b/src/provider.ts index a3b972971..26fa243f8 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -1,6 +1,7 @@ import { ethers } from 'ethers'; import config from './config'; import { i18n, translationErrorMessagesKeys } from './utils/errorMessages'; +import { logger } from './utils/logger'; const INFURA_ID = config.get('INFURA_ID'); @@ -21,6 +22,9 @@ export const NETWORK_IDS = { ARBITRUM_MAINNET: 42161, ARBITRUM_SEPOLIA: 421614, + BASE_MAINNET: 8453, + BASE_SEPOLIA: 84532, + // https://docs.particle.network/developers/other-services/node-service/solana-api SOLANA_MAINNET: 101, SOLANA_TESTNET: 102, @@ -154,6 +158,8 @@ const NETWORK_NAMES = { MORDOR_ETC_TESTNET: 'Ethereum Classic Testnet', ARBITRUM_MAINNET: 'Arbitrum Mainnet', ARBITRUM_SEPOLIA: 'Arbitrum Sepolia', + BASE_MAINNET: 'Base Mainnet', + BASE_SEPOLIA: 'Base Sepolia', }; const NETWORK_NATIVE_TOKENS = { @@ -171,6 +177,8 @@ const NETWORK_NATIVE_TOKENS = { MORDOR_ETC_TESTNET: 'mETC', ARBITRUM_MAINNET: 'ETH', ARBITRUM_SEPOLIA: 'ETH', + BASE_MAINNET: 'ETH', + BASE_SEPOLIA: 'ETH', }; const networkNativeTokensList = [ @@ -244,6 +252,16 @@ const networkNativeTokensList = [ networkId: NETWORK_IDS.ARBITRUM_SEPOLIA, nativeToken: NETWORK_NATIVE_TOKENS.ARBITRUM_SEPOLIA, }, + { + networkName: NETWORK_NAMES.BASE_MAINNET, + networkId: NETWORK_IDS.BASE_MAINNET, + nativeToken: NETWORK_NATIVE_TOKENS.BASE_MAINNET, + }, + { + networkName: NETWORK_NAMES.BASE_SEPOLIA, + networkId: NETWORK_IDS.BASE_SEPOLIA, + nativeToken: NETWORK_NATIVE_TOKENS.BASE_SEPOLIA, + }, ]; export function getNetworkNameById(networkId: number): string { @@ -251,6 +269,10 @@ export function getNetworkNameById(networkId: number): string { item => item.networkId === networkId, ); if (!networkInfo) { + logger.error( + 'getNetworkNameById() error networkNativeTokensList doesnt have info for networkId', + networkId, + ); throw new Error(i18n.__(translationErrorMessagesKeys.INVALID_NETWORK_ID)); } return networkInfo.networkName; @@ -261,6 +283,10 @@ export function getNetworkNativeToken(networkId: number): string { return item.networkId === networkId; }); if (!networkInfo) { + logger.error( + 'getNetworkNativeToken() error networkNativeTokensList doesnt have info for networkId', + networkId, + ); throw new Error(i18n.__(translationErrorMessagesKeys.INVALID_NETWORK_ID)); } return networkInfo.nativeToken; @@ -319,6 +345,18 @@ export function getProvider(networkId: number) { `https://arbitrum-sepolia.infura.io/v3/${INFURA_ID}`; break; + case NETWORK_IDS.BASE_MAINNET: + url = + (process.env.BASE_MAINNET_NODE_HTTP_URL as string) || + `https://base-mainnet.infura.io/v3/${INFURA_ID}`; + break; + + case NETWORK_IDS.BASE_SEPOLIA: + url = + (process.env.BASE_SEPOLIA_NODE_HTTP_URL as string) || + `https://base-sepolia.infura.io/v3/${INFURA_ID}`; + break; + default: { // Use infura const connectionInfo = ethers.providers.InfuraProvider.getUrl( @@ -398,7 +436,19 @@ export function getBlockExplorerApiUrl(networkId: number): string { apiUrl = config.get('ARBITRUM_SEPOLIA_SCAN_API_URL'); apiKey = config.get('ARBITRUM_SEPOLIA_SCAN_API_KEY'); break; + case NETWORK_IDS.BASE_MAINNET: + apiUrl = config.get('BASE_SCAN_API_URL'); + apiKey = config.get('BASE_SCAN_API_KEY'); + break; + case NETWORK_IDS.BASE_SEPOLIA: + apiUrl = config.get('BASE_SEPOLIA_SCAN_API_URL'); + apiKey = config.get('BASE_SEPOLIA_SCAN_API_KEY'); + break; default: + logger.error( + 'getBlockExplorerApiUrl() no url found for networkId', + networkId, + ); throw new Error(i18n.__(translationErrorMessagesKeys.INVALID_NETWORK_ID)); } diff --git a/src/repositories/qfRoundRepository.ts b/src/repositories/qfRoundRepository.ts index bc8e6f3ac..cc24d32dc 100644 --- a/src/repositories/qfRoundRepository.ts +++ b/src/repositories/qfRoundRepository.ts @@ -1,16 +1,34 @@ import { Field, Float, Int, ObjectType, registerEnumType } from 'type-graphql'; import { QfRound } from '../entities/qfRound'; import { AppDataSource } from '../orm'; -import { QfArchivedRoundsOrderBy } from '../resolvers/qfRoundResolver'; +import { + QfArchivedRoundsOrderBy, + QfRoundsArgs, +} from '../resolvers/qfRoundResolver'; const qfRoundEstimatedMatchingParamsCacheDuration = Number( process.env.QF_ROUND_ESTIMATED_MATCHING_CACHE_DURATION || 60000, ); -export const findAllQfRounds = async (): Promise => { - return QfRound.createQueryBuilder('qf_round') - .addOrderBy('qf_round.id', 'DESC') - .getMany(); +export const findAllQfRounds = async ({ + slug, + activeOnly, +}: QfRoundsArgs): Promise => { + const query = QfRound.createQueryBuilder('qf_round').addOrderBy( + 'qf_round.id', + 'DESC', + ); + if (slug) { + query.where('slug = :slug', { slug }); + } + if (activeOnly) { + query.andWhere('"isActive" = true'); + } + if (slug || activeOnly) { + const res = await query.getOne(); + return res ? [res] : []; + } + return query.getMany(); }; export enum QfArchivedRoundsSortType { diff --git a/src/resolvers/donationResolver.test.ts b/src/resolvers/donationResolver.test.ts index 10edd7f07..74916e03c 100644 --- a/src/resolvers/donationResolver.test.ts +++ b/src/resolvers/donationResolver.test.ts @@ -2611,7 +2611,7 @@ function createDonationTestCases() { ); assert.equal( saveDonationResponse.data.errors[0].message, - '"transactionNetworkId" must be one of [1, 3, 5, 100, 137, 10, 11155420, 56, 42220, 44787, 61, 63, 42161, 421614, 101, 102, 103]', + '"transactionNetworkId" must be one of [1, 3, 5, 100, 137, 10, 11155420, 56, 42220, 44787, 61, 63, 42161, 421614, 8453, 84532, 101, 102, 103]', ); }); it('should not throw exception when currency is not valid when currency is USDC.e', async () => { diff --git a/src/resolvers/projectResolver.allProject.test.ts b/src/resolvers/projectResolver.allProject.test.ts index 854400e34..5dfea13bc 100644 --- a/src/resolvers/projectResolver.allProject.test.ts +++ b/src/resolvers/projectResolver.allProject.test.ts @@ -918,7 +918,6 @@ function allProjectsTestCases() { ), ); }); - it('should return projects, filter by accept donation on arbitrum, not return when it doesnt have arbitrum address', async () => { const arbitrumProject = await saveProjectDirectlyToDb({ ...createProjectData(), @@ -964,6 +963,112 @@ function allProjectsTestCases() { ); }); + it('should return projects, filter by accept donation on base', async () => { + const savedProject = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + networkId: NETWORK_IDS.BASE_MAINNET, + }); + const result = await axios.post(graphqlUrl, { + query: fetchMultiFilterAllProjectsQuery, + variables: { + filters: ['AcceptFundOnBase'], + sortingBy: SortingField.Newest, + }, + }); + result.data.data.allProjects.projects.forEach(project => { + assert.isOk( + project.addresses.find( + address => + address.isRecipient === true && + (address.networkId === NETWORK_IDS.BASE_MAINNET || + address.networkId === NETWORK_IDS.BASE_SEPOLIA), + ), + ); + }); + assert.isOk( + result.data.data.allProjects.projects.find( + project => Number(project.id) === Number(savedProject.id), + ), + ); + }); + it('should return projects, filter by accept donation on base', async () => { + const savedProject = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + networkId: NETWORK_IDS.BASE_MAINNET, + }); + const result = await axios.post(graphqlUrl, { + query: fetchMultiFilterAllProjectsQuery, + variables: { + filters: ['AcceptFundOnBase'], + sortingBy: SortingField.Newest, + }, + }); + result.data.data.allProjects.projects.forEach(project => { + assert.isOk( + project.addresses.find( + address => + address.isRecipient === true && + (address.networkId === NETWORK_IDS.BASE_MAINNET || + address.networkId === NETWORK_IDS.BASE_SEPOLIA) && + address.chainType === ChainType.EVM, + ), + ); + }); + assert.isOk( + result.data.data.allProjects.projects.find( + project => Number(project.id) === Number(savedProject.id), + ), + ); + }); + it('should return projects, filter by accept donation on base, not return when it doesnt have base address', async () => { + const baseProject = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + networkId: NETWORK_IDS.BASE_MAINNET, + }); + const polygonProject = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + networkId: NETWORK_IDS.POLYGON, + }); + + const result = await axios.post(graphqlUrl, { + query: fetchMultiFilterAllProjectsQuery, + variables: { + filters: ['AcceptFundOnBase'], + sortingBy: SortingField.Newest, + }, + }); + + result.data.data.allProjects.projects.forEach(project => { + assert.isOk( + project.addresses.find( + address => + address.isRecipient === true && + (address.networkId === NETWORK_IDS.BASE_MAINNET || + address.networkId === NETWORK_IDS.BASE_SEPOLIA) && + address.chainType === ChainType.EVM, + ), + ); + }); + assert.isNotOk( + result.data.data.allProjects.projects.find( + project => Number(project.id) === Number(polygonProject.id), + ), + ); + assert.isOk( + result.data.data.allProjects.projects.find( + project => Number(project.id) === Number(baseProject.id), + ), + ); + }); + it('should return projects, filter by accept donation on mainnet', async () => { const savedProject = await saveProjectDirectlyToDb({ ...createProjectData(), diff --git a/src/resolvers/projectResolver.test.ts b/src/resolvers/projectResolver.test.ts index 5d82a7df0..a2c90da77 100644 --- a/src/resolvers/projectResolver.test.ts +++ b/src/resolvers/projectResolver.test.ts @@ -117,7 +117,6 @@ describe( 'addRecipientAddressToProject test cases --->', addRecipientAddressToProjectTestCases, ); - describe('projectsByUserId test cases --->', projectsByUserIdTestCases); describe('deactivateProject test cases --->', deactivateProjectTestCases); diff --git a/src/resolvers/projectResolver.ts b/src/resolvers/projectResolver.ts index 456d37b6c..cfffb7cdd 100644 --- a/src/resolvers/projectResolver.ts +++ b/src/resolvers/projectResolver.ts @@ -527,6 +527,11 @@ export class ProjectResolver { networkIds.push(NETWORK_IDS.ARBITRUM_SEPOLIA); return; + case FilterField.AcceptFundOnBase: + networkIds.push(NETWORK_IDS.BASE_MAINNET); + networkIds.push(NETWORK_IDS.BASE_SEPOLIA); + return; + case FilterField.AcceptFundOnPolygon: networkIds.push(NETWORK_IDS.POLYGON); return; @@ -822,27 +827,65 @@ export class ProjectResolver { @Arg('connectedWalletUserId', _type => Int, { nullable: true }) connectedWalletUserId: number, @Ctx() { req: { user } }: ApolloContext, + @Info() info: any, ) { + const fields = graphqlFields(info); + let query = this.projectRepository .createQueryBuilder('project') .where(`project.id=:id`, { id, }) - .leftJoinAndSelect('project.status', 'status') - .leftJoinAndSelect( - 'project.categories', - 'categories', - 'categories.isActive = :isActive', - { isActive: true }, - ) - .leftJoinAndSelect('categories.mainCategory', 'mainCategory') - .leftJoinAndSelect('project.addresses', 'addresses') - .leftJoinAndSelect('project.socialMedia', 'socialMedia') - .leftJoinAndSelect('project.anchorContracts', 'anchor_contract_address') - .leftJoinAndSelect('project.organization', 'organization') - .leftJoin('project.adminUser', 'user') - .addSelect(publicSelectionFields); // aliased selection - query = ProjectResolver.addUserReaction(query, connectedWalletUserId, user); + .leftJoinAndSelect('project.status', 'status'); + + if (fields.categories) { + query = query + .leftJoinAndSelect( + 'project.categories', + 'categories', + 'categories.isActive = :isActive', + { isActive: true }, + ) + .leftJoinAndSelect('categories.mainCategory', 'mainCategory'); + } + if (fields.organization) { + query = query.leftJoinAndSelect('project.organization', 'organization'); + } + if (fields.addresses) { + query = query.leftJoinAndSelect('project.addresses', 'addresses'); + } + if (fields.socialMedia) { + query = query.leftJoinAndSelect('project.socialMedia', 'socialMedia'); + } + if (fields.anchorContracts) { + query = query.leftJoinAndSelect( + 'project.anchorContracts', + 'anchor_contract_address', + ); + } + if (fields.adminUser) { + const adminUserFields = Object.keys(fields.adminUser).map( + field => `user.${field}`, + ); + const filterByPublicFields = publicSelectionFields.filter(field => + adminUserFields.includes(field), + ); + query = query + .leftJoin('project.adminUser', 'user') + .addSelect( + filterByPublicFields.length > 0 + ? filterByPublicFields + : publicSelectionFields, + ); // aliased selection + } + if (fields.reaction) { + query = ProjectResolver.addUserReaction( + query, + connectedWalletUserId, + user, + ); + } + const project = await query.getOne(); canUserVisitProject(project, user?.userId); @@ -961,7 +1004,11 @@ export class ProjectResolver { ); query = query .leftJoin('project.adminUser', 'user') - .addSelect(filterByPublicFields); // aliased selection + .addSelect( + filterByPublicFields.length > 0 + ? filterByPublicFields + : publicSelectionFields, + ); // aliased selection } if (fields.reaction) { query = ProjectResolver.addUserReaction( diff --git a/src/resolvers/projectVerificationFormResolver.test.ts b/src/resolvers/projectVerificationFormResolver.test.ts index 695e86d59..b14dc0b59 100644 --- a/src/resolvers/projectVerificationFormResolver.test.ts +++ b/src/resolvers/projectVerificationFormResolver.test.ts @@ -321,6 +321,16 @@ function updateProjectVerificationFormMutationTestCases() { networkId: NETWORK_IDS.ARBITRUM_SEPOLIA, title: 'test title', }, + { + address: generateRandomEtheriumAddress(), + networkId: NETWORK_IDS.BASE_MAINNET, + title: 'test title', + }, + { + address: generateRandomEtheriumAddress(), + networkId: NETWORK_IDS.BASE_SEPOLIA, + title: 'test title', + }, { address: generateRandomEtheriumAddress(), networkId: NETWORK_IDS.ETC, diff --git a/src/resolvers/qfRoundResolver.ts b/src/resolvers/qfRoundResolver.ts index 3201278c4..562921483 100644 --- a/src/resolvers/qfRoundResolver.ts +++ b/src/resolvers/qfRoundResolver.ts @@ -82,11 +82,24 @@ class QfArchivedRoundsArgs { orderBy: QfArchivedRoundsOrderBy; } +@Service() +@ArgsType() +export class QfRoundsArgs { + @Field(_type => String, { nullable: true }) + slug?: string; + + @Field(_type => Boolean, { nullable: true }) + activeOnly?: boolean; +} + @Resolver(_of => User) export class QfRoundResolver { @Query(_returns => [QfRound], { nullable: true }) - async qfRounds() { - return findAllQfRounds(); + async qfRounds( + @Args() + { slug, activeOnly }: QfRoundsArgs, + ) { + return findAllQfRounds({ slug, activeOnly }); } @Query(_returns => [QFArchivedRounds], { nullable: true }) diff --git a/src/server/adminJs/tabs/donationTab.ts b/src/server/adminJs/tabs/donationTab.ts index 5e4574756..ffc2b4f20 100644 --- a/src/server/adminJs/tabs/donationTab.ts +++ b/src/server/adminJs/tabs/donationTab.ts @@ -603,6 +603,8 @@ export const donationTab = { { value: NETWORK_IDS.CELO_ALFAJORES, label: 'Alfajores' }, { value: NETWORK_IDS.ARBITRUM_MAINNET, label: 'Arbitrum' }, { value: NETWORK_IDS.ARBITRUM_SEPOLIA, label: 'Arbitrum Sepolia' }, + { value: NETWORK_IDS.BASE_MAINNET, label: 'Base' }, + { value: NETWORK_IDS.BASE_SEPOLIA, label: 'Base Sepolia' }, ], isVisible: { list: true, diff --git a/src/server/adminJs/tabs/qfRoundTab.ts b/src/server/adminJs/tabs/qfRoundTab.ts index 7ac744ff8..3a6ed6eb6 100644 --- a/src/server/adminJs/tabs/qfRoundTab.ts +++ b/src/server/adminJs/tabs/qfRoundTab.ts @@ -121,6 +121,8 @@ const availableNetworkValues = [ }, { value: NETWORK_IDS.ARBITRUM_MAINNET, label: 'ARBITRUM MAINNET' }, { value: NETWORK_IDS.ARBITRUM_SEPOLIA, label: 'ARBITRUM SEPOLIA' }, + { value: NETWORK_IDS.BASE_MAINNET, label: 'BASE MAINNET' }, + { value: NETWORK_IDS.BASE_SEPOLIA, label: 'BASE SEPOLIA' }, { value: NETWORK_IDS.XDAI, label: 'XDAI' }, { value: NETWORK_IDS.BSC, label: 'BSC' }, ]; diff --git a/src/server/adminJs/tabs/tokenTab.ts b/src/server/adminJs/tabs/tokenTab.ts index b6a83f959..65360652f 100644 --- a/src/server/adminJs/tabs/tokenTab.ts +++ b/src/server/adminJs/tabs/tokenTab.ts @@ -122,6 +122,7 @@ export const createToken = async ( isGivbackEligible, mainnetAddress, name, + coingeckoId, networkId, symbol, organizations, @@ -133,6 +134,7 @@ export const createToken = async ( address: address?.toLowerCase(), mainnetAddress: mainnetAddress?.toLowerCase(), isGivbackEligible, + coingeckoId, decimals: Number(decimals), networkId: Number(networkId), }); @@ -197,6 +199,8 @@ export const generateTokenTab = async () => { }, { value: NETWORK_IDS.ARBITRUM_MAINNET, label: 'ARBITRUM MAINNET' }, { value: NETWORK_IDS.ARBITRUM_SEPOLIA, label: 'ARBITRUM SEPOLIA' }, + { value: NETWORK_IDS.BASE_MAINNET, label: 'BASE MAINNET' }, + { value: NETWORK_IDS.BASE_SEPOLIA, label: 'BASE SEPOLIA' }, { value: NETWORK_IDS.XDAI, label: 'XDAI' }, { value: NETWORK_IDS.BSC, label: 'BSC' }, { value: NETWORK_IDS.ETC, label: 'Ethereum Classic' }, diff --git a/src/services/chains/index.test.ts b/src/services/chains/index.test.ts index dd7958ac4..fc03fa910 100644 --- a/src/services/chains/index.test.ts +++ b/src/services/chains/index.test.ts @@ -540,6 +540,44 @@ function getTransactionDetailTestCases() { assert.equal(transactionInfo.amount, amount); }); + it('should return transaction detail for normal transfer on Base Mainnet', async () => { + // https://basescan.org/tx/0x1cbf53e5a9a0874b9ad97316e4f2e1782e24bec318bacd183d3f48052bfe1523 + + const amount = 0.0032; + const transactionInfo = await getTransactionInfoFromNetwork({ + txHash: + '0x1cbf53e5a9a0874b9ad97316e4f2e1782e24bec318bacd183d3f48052bfe1523', + symbol: 'ETH', + networkId: NETWORK_IDS.BASE_MAINNET, + fromAddress: '0xbaed383ede0e5d9d72430661f3285daa77e9439f', + toAddress: '0xa5401000d255dbb154deb756b82dd5105486d8c9', + amount, + timestamp: 1716445331, + }); + assert.isOk(transactionInfo); + assert.equal(transactionInfo.currency, 'ETH'); + assert.equal(transactionInfo.amount, amount); + }); + + it('should return transaction detail for normal transfer on Base Sepolia', async () => { + // https://sepolia.basescan.org/tx/0x66fdfe46de46fa1fbb77de642cc778cafc85943204039f69694aee6121f764f4 + + const amount = 0.001; + const transactionInfo = await getTransactionInfoFromNetwork({ + txHash: + '0x66fdfe46de46fa1fbb77de642cc778cafc85943204039f69694aee6121f764f4', + symbol: 'ETH', + networkId: NETWORK_IDS.BASE_SEPOLIA, + fromAddress: '0x9cab0c7ff1c6250e641f4dcd4d9cd9db83bffb71', + toAddress: '0xd7eedf8422ababfbcafc0797e809ceae742fc142', + amount, + timestamp: 1716445488, + }); + assert.isOk(transactionInfo); + assert.equal(transactionInfo.currency, 'ETH'); + assert.equal(transactionInfo.amount, amount); + }); + it('should return transaction detail for OP token transfer on optimistic', async () => { // https://optimistic.etherscan.io/tx/0xf11be189d967831bb8a76656882eeeac944a799bd222acbd556f2156fdc02db4 const amount = 0.453549908802477308; diff --git a/src/services/googleSheets.ts b/src/services/googleSheets.ts index abb340cd7..a888a32b6 100644 --- a/src/services/googleSheets.ts +++ b/src/services/googleSheets.ts @@ -159,9 +159,7 @@ export const addQfRoundDonationsSheetToSpreadsheet = async (params: { }): Promise => { try { const spreadSheet = await initQfRoundDonationsSpreadsheet(); - const currentDate = moment().toDate(); - const headers = [ 'projectName', 'addresses', @@ -178,14 +176,30 @@ export const addQfRoundDonationsSheetToSpreadsheet = async (params: { 'uniqueUserIdsAfterAnalysis', 'projectOwnerEmail', ]; + const { rows, qfRoundId } = params; const sheet = await spreadSheet.addSheet({ headerValues: headers, title: `QfRound -${qfRoundId} - ${currentDate.toDateString()} ${currentDate.getTime()}`, }); + + // Modify rows to truncate cells with more than 50000 characters and add "..." + const modifiedRows = rows.map(row => { + const modifiedRow = {}; + Object.keys(row).forEach(key => { + if (typeof row[key] === 'string' && row[key].length > 50000) { + // Truncate the string to the maximum allowed length and append "..." + modifiedRow[key] = row[key].substring(0, 49990) + '...'; + } else { + modifiedRow[key] = row[key]; + } + }); + return modifiedRow; + }); + logger.debug('addQfRoundDonationsSheetToSpreadsheet', params); - await sheet.addRows(rows); + await sheet.addRows(modifiedRows); } catch (e) { logger.error('addQfRoundDonationsSheetToSpreadsheet error', e); throw e; diff --git a/src/utils/networksConfig.ts b/src/utils/networksConfig.ts index e1cf9b752..80d27e3f2 100644 --- a/src/utils/networksConfig.ts +++ b/src/utils/networksConfig.ts @@ -37,6 +37,8 @@ const networksConfig = { }, '42161': { blockExplorer: 'https://arbiscan.io/' }, '421614': { blockExplorer: 'https://sepolia.arbiscan.io/' }, + '8453': { blockExplorer: 'https://basescan.org/' }, + '84532': { blockExplorer: 'https://sepolia.basescan.org/' }, }; export default networksConfig; diff --git a/src/utils/validators/graphqlQueryValidators.ts b/src/utils/validators/graphqlQueryValidators.ts index ea232be2b..d859eb73e 100644 --- a/src/utils/validators/graphqlQueryValidators.ts +++ b/src/utils/validators/graphqlQueryValidators.ts @@ -243,6 +243,8 @@ const managingFundsValidator = Joi.object({ NETWORK_IDS.CELO_ALFAJORES, NETWORK_IDS.ARBITRUM_MAINNET, NETWORK_IDS.ARBITRUM_SEPOLIA, + NETWORK_IDS.BASE_MAINNET, + NETWORK_IDS.BASE_SEPOLIA, NETWORK_IDS.OPTIMISTIC, NETWORK_IDS.OPTIMISM_SEPOLIA, NETWORK_IDS.XDAI, diff --git a/src/utils/validators/projectValidator.ts b/src/utils/validators/projectValidator.ts index e1027a2b4..bb3997f91 100644 --- a/src/utils/validators/projectValidator.ts +++ b/src/utils/validators/projectValidator.ts @@ -141,6 +141,8 @@ export const isWalletAddressSmartContract = async ( NETWORK_IDS.CELO_ALFAJORES, NETWORK_IDS.ARBITRUM_MAINNET, NETWORK_IDS.ARBITRUM_SEPOLIA, + NETWORK_IDS.BASE_MAINNET, + NETWORK_IDS.BASE_SEPOLIA, ]; const _isSmartContracts = await Promise.all( diff --git a/test/graphqlQueries.ts b/test/graphqlQueries.ts index 094a908d3..eed85dfca 100644 --- a/test/graphqlQueries.ts +++ b/test/graphqlQueries.ts @@ -1056,7 +1056,6 @@ export const fetchProjectBySlugQuery = ` sortingField createdAt updatedAt - } givbackFactor projectPower { diff --git a/test/pre-test-scripts.ts b/test/pre-test-scripts.ts index b33f1c2f8..a5c9e6dc5 100644 --- a/test/pre-test-scripts.ts +++ b/test/pre-test-scripts.ts @@ -185,6 +185,34 @@ async function seedTokens() { } await Token.create(tokenData as Token).save(); } + for (const token of SEED_DATA.TOKENS.base_mainnet) { + const tokenData = { + ...token, + networkId: NETWORK_IDS.BASE_MAINNET, + isGivbackEligible: true, + }; + if (token.symbol === 'GIV') { + // TODO I'm not sure whether we support GIV or not + (tokenData as any).order = 1; + } else if (token.symbol === 'ETH') { + (tokenData as any).order = 2; + } + await Token.create(tokenData as Token).save(); + } + for (const token of SEED_DATA.TOKENS.base_sepolia) { + const tokenData = { + ...token, + networkId: NETWORK_IDS.BASE_SEPOLIA, + isGivbackEligible: true, + }; + if (token.symbol === 'GIV') { + // TODO I'm not sure whether we support GIV or not + (tokenData as any).order = 1; + } else if (token.symbol === 'ETH') { + (tokenData as any).order = 2; + } + await Token.create(tokenData as Token).save(); + } for (const token of SEED_DATA.TOKENS.optimistic) { const tokenData = { ...token, diff --git a/test/testUtils.ts b/test/testUtils.ts index bff29abb5..8a6e5171d 100644 --- a/test/testUtils.ts +++ b/test/testUtils.ts @@ -1732,6 +1732,24 @@ export const SEED_DATA = { coingeckoId: 'weth', }, ], + base_mainnet: [ + { + name: 'Base ETH', + symbol: 'ETH', + address: '0x0000000000000000000000000000000000000000', + decimals: 18, + coingeckoId: 'ethereum', + }, + ], + base_sepolia: [ + { + name: 'Base Sepolia native token', + symbol: 'ETH', + address: '0x0000000000000000000000000000000000000000', + decimals: 18, + coingeckoId: 'ethereum', + }, + ], }, };