From 6e72b25752fa49ed5dd06bf7353d669f88dc9631 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Thu, 4 Jan 2024 18:33:49 +0330 Subject: [PATCH 01/34] Verify transfer spl-token on solana, supporting multi chain solana related to #1175 --- .github/workflows/develop-pipeline.yml | 4 +- .github/workflows/master-pipeline.yml | 4 +- .github/workflows/staging-pipeline.yml | 4 +- config/example.env | 4 +- config/test.env | 5 +- migration/data/seedTokens.ts | 2 +- src/provider.ts | 6 +- src/resolvers/donationResolver.test.ts | 4 +- .../projectVerificationFormResolver.test.ts | 2 +- src/services/chains/index.test.ts | 48 +++++++++- .../chains/solana/transactionService.ts | 92 ++++++++++++++++--- src/services/donationService.ts | 2 +- .../validators/graphqlQueryValidators.ts | 2 +- test/pre-test-scripts.ts | 22 ++++- test/testUtils.ts | 24 ++++- 15 files changed, 192 insertions(+), 33 deletions(-) diff --git a/.github/workflows/develop-pipeline.yml b/.github/workflows/develop-pipeline.yml index d98808e69..c377286ae 100644 --- a/.github/workflows/develop-pipeline.yml +++ b/.github/workflows/develop-pipeline.yml @@ -65,7 +65,9 @@ jobs: CELO_ALFAJORES_SCAN_API_KEY: ${{ secrets.CELO_ALFAJORES_SCAN_API_KEY }} MORDOR_ETC_TESTNET: ${{ secrets.MORDOR_ETC_TESTNET }} ETC_NODE_HTTP_URL: ${{ secrets.ETC_NODE_HTTP_URL }} - SOLANA_NODE_RPC_URL: ${{ secrets.SOLANA_NODE_RPC_URL }} + SOLANA_TEST_NODE_RPC_URL: ${{ secrets.SOLANA_TEST_NODE_RPC_URL }} + SOLANA_DEVNET_NODE_RPC_URL: ${{ secrets.SOLANA_DEVNET_NODE_RPC_URL }} + SOLANA_MAINNET_NODE_RPC_URL: ${{ secrets.SOLANA_MAINNET_NODE_RPC_URL }} publish: needs: test diff --git a/.github/workflows/master-pipeline.yml b/.github/workflows/master-pipeline.yml index acaa5fb41..77c44f8d7 100644 --- a/.github/workflows/master-pipeline.yml +++ b/.github/workflows/master-pipeline.yml @@ -65,7 +65,9 @@ jobs: CELO_ALFAJORES_SCAN_API_KEY: ${{ secrets.CELO_ALFAJORES_SCAN_API_KEY }} MORDOR_ETC_TESTNET: ${{ secrets.MORDOR_ETC_TESTNET }} ETC_NODE_HTTP_URL: ${{ secrets.ETC_NODE_HTTP_URL }} - SOLANA_NODE_RPC_URL: ${{ secrets.SOLANA_NODE_RPC_URL }} + SOLANA_TEST_NODE_RPC_URL: ${{ secrets.SOLANA_TEST_NODE_RPC_URL }} + SOLANA_DEVNET_NODE_RPC_URL: ${{ secrets.SOLANA_DEVNET_NODE_RPC_URL }} + SOLANA_MAINNET_NODE_RPC_URL: ${{ secrets.SOLANA_MAINNET_NODE_RPC_URL }} publish: needs: test diff --git a/.github/workflows/staging-pipeline.yml b/.github/workflows/staging-pipeline.yml index e0530e7b2..96f245e76 100644 --- a/.github/workflows/staging-pipeline.yml +++ b/.github/workflows/staging-pipeline.yml @@ -103,7 +103,9 @@ jobs: MORDOR_ETC_TESTNET: ${{ secrets.MORDOR_ETC_TESTNET }} ETC_NODE_HTTP_URL: ${{ secrets.ETC_NODE_HTTP_URL }} DROP_DATABASE: ${{ secrets.DROP_DATABASE }} - SOLANA_NODE_RPC_URL: ${{ secrets.SOLANA_NODE_RPC_URL }} + SOLANA_TEST_NODE_RPC_URL: ${{ secrets.SOLANA_TEST_NODE_RPC_URL }} + SOLANA_DEVNET_NODE_RPC_URL: ${{ secrets.SOLANA_DEVNET_NODE_RPC_URL }} + SOLANA_MAINNET_NODE_RPC_URL: ${{ secrets.SOLANA_MAINNET_NODE_RPC_URL }} publish: needs: test diff --git a/config/example.env b/config/example.env index bbb004a54..44a6ac344 100644 --- a/config/example.env +++ b/config/example.env @@ -233,4 +233,6 @@ DISABLE_SERVER_RATE_LIMITER=false DISABLE_SERVER_CORS=false -SOLANA_NODE_RPC_URL= +SOLANA_MAINNET_NODE_RPC_URL= +SOLANA_DEVNET_NODE_RPC_URL= +SOLANA_TEST_NODE_RPC_URL= diff --git a/config/test.env b/config/test.env index db5f9b131..86c6273e2 100644 --- a/config/test.env +++ b/config/test.env @@ -195,4 +195,7 @@ MATCHING_FUND_DONATIONS_FROM_ADDRESS=0x6e8873085530406995170Da467010565968C7C62 # Rate Limit config DISABLE_SERVER_RATE_LIMITER=false DISABLE_SERVER_CORS=false -SOLANA_NODE_RPC_URL= + +SOLANA_MAINNET_NODE_RPC_URL= +SOLANA_DEVNET_NODE_RPC_URL= +SOLANA_TEST_NODE_RPC_URL= diff --git a/migration/data/seedTokens.ts b/migration/data/seedTokens.ts index 1e26e26cb..028632780 100644 --- a/migration/data/seedTokens.ts +++ b/migration/data/seedTokens.ts @@ -1255,7 +1255,7 @@ const seedTokens: ITokenData[] = [ symbol: 'SOL', address: SOLANA_SYSTEM_PROGRAM, decimals: 9, - networkId: NETWORK_IDS.SOLANA, + networkId: NETWORK_IDS.SOLANA_MAINNET, chainType: ChainType.SOLANA, coingeckoId: COINGECKO_TOKEN_IDS.SOLANA, }, diff --git a/src/provider.ts b/src/provider.ts index ce11d0217..8d8f14e14 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -18,7 +18,11 @@ export const NETWORK_IDS = { CELO_ALFAJORES: 44787, ETC: 61, MORDOR_ETC_TESTNET: 63, - SOLANA: 0, + + // https://docs.particle.network/developers/other-services/node-service/solana-api + SOLANA_MAINNET: 101, + SOLANA_TESTNET: 102, + SOLANA_DEVNET: 103, }; export const NETWORKS_IDS_TO_NAME = { diff --git a/src/resolvers/donationResolver.test.ts b/src/resolvers/donationResolver.test.ts index b8072ca64..c815f9a4c 100644 --- a/src/resolvers/donationResolver.test.ts +++ b/src/resolvers/donationResolver.test.ts @@ -838,7 +838,7 @@ function createDonationTestCases() { query: createDonationMutation, variables: { projectId: project.id, - transactionNetworkId: NETWORK_IDS.SOLANA, + transactionNetworkId: NETWORK_IDS.SOLANA_MAINNET, transactionId, nonce: 1, amount: 10, @@ -882,7 +882,7 @@ function createDonationTestCases() { query: createDonationMutation, variables: { projectId: project.id, - transactionNetworkId: NETWORK_IDS.SOLANA, + transactionNetworkId: NETWORK_IDS.SOLANA_MAINNET, transactionId, nonce: 0, amount: 100, diff --git a/src/resolvers/projectVerificationFormResolver.test.ts b/src/resolvers/projectVerificationFormResolver.test.ts index 074abfdf1..ae2c23546 100644 --- a/src/resolvers/projectVerificationFormResolver.test.ts +++ b/src/resolvers/projectVerificationFormResolver.test.ts @@ -324,7 +324,7 @@ function updateProjectVerificationFormMutationTestCases() { }, { address: generateRandomSolanaAddress(), - networkId: NETWORK_IDS.SOLANA, + networkId: NETWORK_IDS.SOLANA_MAINNET, title: 'test title', chainType: ChainType.SOLANA, }, diff --git a/src/services/chains/index.test.ts b/src/services/chains/index.test.ts index e0622496a..1a8bd34a2 100644 --- a/src/services/chains/index.test.ts +++ b/src/services/chains/index.test.ts @@ -881,7 +881,7 @@ function getTransactionDetailTestCases() { '5GQGAgGfMNypB5GN4Pp2t3mEMky89bbpZwNDaDh1LJXopVm3bgSxFUgEJ4tEjf2NdibxX4NiiA752Ya2hzg2nqj8', symbol: 'SOL', chainType: ChainType.SOLANA, - networkId: NETWORK_IDS.SOLANA, + networkId: NETWORK_IDS.SOLANA_MAINNET, fromAddress: '5GECDSGSWmMuw6nMfmdBLapa91ZHDZeHqRP1fqvQokjY', toAddress: 'DvWdrYYkwyM9mnTetpr3HBHUBKZ22QdbFEXQ8oquE7Zb', timestamp: 1702931400, @@ -900,7 +900,7 @@ function getTransactionDetailTestCases() { '3nzHwgxAu7mKw1dhGTVmqzY8Yet3kGWWqP5kr5D2fw1HzqPjqDGDe6xT5PguKXk8nAJcK4GpBEKWw7EzoLykKkCx', symbol: 'SOL', chainType: ChainType.SOLANA, - networkId: NETWORK_IDS.SOLANA, + networkId: NETWORK_IDS.SOLANA_DEVNET, fromAddress: '9B5XszUGdMaxCZ7uSQhPzdks5ZQSmWxrmzCSvtJ6Ns6g', toAddress: 'GEhUKKZeENY1TmaavqvLJ5GbbQs9GkzECFSE2bpjzz3k', timestamp: 1701289800, @@ -921,7 +921,7 @@ function getTransactionDetailTestCases() { '5GQGAgGfMNypB5GN4Pp2t3mEMky89bbpZwNDaDh1LJXopVm3bgSxFUgEJ4tEjf2NdibxX4NiiA752Ya2hzg2nqj8', symbol: 'SOL', chainType: ChainType.SOLANA, - networkId: NETWORK_IDS.SOLANA, + networkId: NETWORK_IDS.SOLANA_DEVNET, fromAddress: '5GECDSGSWmMuw6nMfmdBLapa91ZHDZeHqRP1fqvQokjY', toAddress: 'DvWdrYYkwyM9mnTetpr3HBHUBKZ22QdbFEXQ8oquE7Zb', timestamp: 1702931400 + ONE_DAY, @@ -933,4 +933,46 @@ function getTransactionDetailTestCases() { errorMessages.TRANSACTION_CANT_BE_OLDER_THAN_DONATION, ); }); + + it('should return transaction detail for spl-token transfer on Solana', async () => { + // https://solscan.io/tx/2tm14GVsDwXpMzxZzpEWyQnfzcUEv1DZQVQb6VdbsHcV8StoMbBtuQTkW1LJ8RhKKrAL18gbm181NgzuusiQfZ16?cluster=devnet + const amount = 7; + const transactionInfo = await getTransactionInfoFromNetwork({ + txHash: + '2tm14GVsDwXpMzxZzpEWyQnfzcUEv1DZQVQb6VdbsHcV8StoMbBtuQTkW1LJ8RhKKrAL18gbm181NgzuusiQfZ16', + symbol: 'TEST-SPL-TOKEN', + chainType: ChainType.SOLANA, + networkId: NETWORK_IDS.SOLANA_DEVNET, + fromAddress: 'BxUK9tDLeMT7AkTR2jBTQQYUxGGw6nuWbQqGtiHHfftn', + toAddress: 'FAMREy7d73N5jPdoKowQ4QFm6DKPWuYxZh6cwjNAbpkY', + timestamp: 1704357745, + amount, + }); + assert.isOk(transactionInfo); + assert.equal(transactionInfo.currency, 'TEST-SPL-TOKEN'); + assert.equal(transactionInfo.amount, amount); + }); + + it('should return error when transaction time is newer than sent timestamp for spl-token transfer on Solana', async () => { + // https://explorer.solana.com/tx/2tm14GVsDwXpMzxZzpEWyQnfzcUEv1DZQVQb6VdbsHcV8StoMbBtuQTkW1LJ8RhKKrAL18gbm181NgzuusiQfZ16?cluster=devnet + + const amount = 7; + const badFunc = async () => { + await getTransactionInfoFromNetwork({ + txHash: + '2tm14GVsDwXpMzxZzpEWyQnfzcUEv1DZQVQb6VdbsHcV8StoMbBtuQTkW1LJ8RhKKrAL18gbm181NgzuusiQfZ16', + symbol: 'TEST-SPL-TOKEN', + chainType: ChainType.SOLANA, + networkId: NETWORK_IDS.SOLANA_DEVNET, + fromAddress: 'BxUK9tDLeMT7AkTR2jBTQQYUxGGw6nuWbQqGtiHHfftn', + toAddress: 'FAMREy7d73N5jPdoKowQ4QFm6DKPWuYxZh6cwjNAbpkY', + timestamp: 1704357745 + ONE_DAY, + amount, + }); + }; + await assertThrowsAsync( + badFunc, + errorMessages.TRANSACTION_CANT_BE_OLDER_THAN_DONATION, + ); + }); } diff --git a/src/services/chains/solana/transactionService.ts b/src/services/chains/solana/transactionService.ts index 7c368aebb..b9184a24b 100644 --- a/src/services/chains/solana/transactionService.ts +++ b/src/services/chains/solana/transactionService.ts @@ -10,16 +10,33 @@ import { i18n, translationErrorMessagesKeys, } from '../../../utils/errorMessages'; +import { + findTokenByNetworkAndAddress, + findTokenByNetworkAndSymbol, +} from '../../../utils/tokenUtils'; +import { NETWORK_IDS } from '../../../provider'; -let solanaProvider; +const solanaProviders = new Map(); -const getSolanaWebProvider = () => { - if (solanaProvider) { - return solanaProvider; +const getSolanaWebProvider = (chainId: number) => { + if (solanaProviders.has(chainId)) { + return solanaProviders.get(chainId); } - const SOLANA_NODE_RPC_URL = process.env.SOLANA_NODE_RPC_URL as string; - solanaProvider = new SolanaWeb3.Connection(SOLANA_NODE_RPC_URL); - return solanaProvider; + if (chainId === NETWORK_IDS.SOLANA_MAINNET) { + solanaProviders[chainId] = new SolanaWeb3.Connection( + process.env.SOLANA_MAINNET_NODE_RPC_URL as string, + ); + } else if (chainId === NETWORK_IDS.SOLANA_TESTNET) { + solanaProviders[chainId] = new SolanaWeb3.Connection( + process.env.SOLANA_TEST_NODE_RPC_URL as string, + ); + } else { + // DEVNET + solanaProviders[chainId] = new SolanaWeb3.Connection( + process.env.SOLANA_MAINNET_NODE_RPC_URL as string, + ); + } + return solanaProviders[chainId]; }; export const getSolanaTransactionDetailForSolanaTransfer = async ( @@ -29,9 +46,9 @@ export const getSolanaTransactionDetailForSolanaTransfer = async ( const SOL_CURRENCY_TYPE = 'SOL'; try { - const result = await getSolanaWebProvider().getParsedTransaction( - params.txHash, - ); + const result = await getSolanaWebProvider( + params.networkId, + ).getParsedTransaction(params.txHash); const data = result?.transaction?.message?.instructions?.find( instruction => instruction?.parsed?.type === TRANSFER_INSTRUCTION_TYPE && @@ -62,10 +79,55 @@ export const getSolanaTransactionDetailForSolanaTransfer = async ( } }; -function getTransactionDetailForTokenTransfer( - input: TransactionDetailInput, -): Promise { - throw new Error('Not Implemented'); +async function getTransactionDetailForSplTokenTransfer( + params: TransactionDetailInput, +): Promise { + try { + const SPL_TOKEN_TRANSFER_INSTRUCTION_TYPE = 'spl-token'; + const TRANSFER_CHECK_TYPE = 'transferChecked'; + + const result = await getSolanaWebProvider( + params.networkId, + ).getParsedTransaction(params.txHash); + const token = await findTokenByNetworkAndSymbol( + params.networkId, + params.symbol, + ); + + const data = result?.transaction?.message?.instructions?.find( + instruction => + instruction?.program === SPL_TOKEN_TRANSFER_INSTRUCTION_TYPE && + instruction?.parsed?.type === TRANSFER_CHECK_TYPE && + instruction?.parsed?.info.authority === params.fromAddress && + instruction?.parsed?.info.mint === token?.address, + ); + const toAddressBalance = result?.meta?.postTokenBalances?.find( + balance => + balance.owner === params.toAddress && balance.mint === token?.address, + ); + if (!data || !toAddressBalance) { + return null; + } + const parsedData = data as ParsedInstruction; + + const txInfo = parsedData.parsed.info; + if (!txInfo) { + return null; + } + return { + from: txInfo.authority, + + // we already check toAddressBalance.owner === params.toAddress + to: toAddressBalance?.owner, + amount: txInfo.tokenAmount?.uiAmount, + currency: params.symbol, + timestamp: result!.blockTime as number, + hash: params.txHash, + }; + } catch (e) { + logger.error('getSolanaTransactionDetailForNormalTransfer error', e); + return null; + } } export async function getSolanaTransactionInfoFromNetwork( @@ -76,7 +138,7 @@ export async function getSolanaTransactionInfoFromNetwork( if (nativeToken.toLowerCase() === input.symbol.toLowerCase()) { txData = await getSolanaTransactionDetailForSolanaTransfer(input); } else { - txData = await getTransactionDetailForTokenTransfer(input); + txData = await getTransactionDetailForSplTokenTransfer(input); } if (!txData) { throw new Error( diff --git a/src/services/donationService.ts b/src/services/donationService.ts index c1929278c..388f7c601 100644 --- a/src/services/donationService.ts +++ b/src/services/donationService.ts @@ -61,7 +61,7 @@ export const updateDonationPricesAndValues = async ( const coingeckoAdapter = new CoingeckoPriceAdapter(); const solanaPriceUsd = await coingeckoAdapter.getTokenPrice({ symbol: token.coingeckoId, - networkId: NETWORK_IDS.SOLANA, + networkId: NETWORK_IDS.SOLANA_MAINNET, }); donation.priceUsd = toFixNumber(solanaPriceUsd, 4); donation.valueUsd = toFixNumber(donation.amount * solanaPriceUsd, 4); diff --git a/src/utils/validators/graphqlQueryValidators.ts b/src/utils/validators/graphqlQueryValidators.ts index 14421973d..bf803d380 100644 --- a/src/utils/validators/graphqlQueryValidators.ts +++ b/src/utils/validators/graphqlQueryValidators.ts @@ -177,7 +177,7 @@ const managingFundsValidator = Joi.object({ Joi.string().required().pattern(solanaWalletAddressRegex), ), networkId: Joi.number()?.valid( - NETWORK_IDS.SOLANA, // Solana + NETWORK_IDS.SOLANA_MAINNET, // Solana NETWORK_IDS.MAIN_NET, NETWORK_IDS.ROPSTEN, NETWORK_IDS.GOERLI, diff --git a/test/pre-test-scripts.ts b/test/pre-test-scripts.ts index e776768dc..c1fad384e 100644 --- a/test/pre-test-scripts.ts +++ b/test/pre-test-scripts.ts @@ -204,16 +204,34 @@ async function seedTokens() { } await Token.create(tokenData as Token).save(); } - for (const token of SEED_DATA.TOKENS.solana) { + for (const token of SEED_DATA.TOKENS.solana_mainnet) { const tokenData = { ...token, - networkId: NETWORK_IDS.SOLANA, + networkId: NETWORK_IDS.SOLANA_MAINNET, isGivbackEligible: false, chainType: ChainType.SOLANA, coingeckoId: COINGECKO_TOKEN_IDS.SOLANA, }; await Token.create(tokenData as Token).save(); } + for (const token of SEED_DATA.TOKENS.solana_devnet) { + const tokenData = { + ...token, + networkId: NETWORK_IDS.SOLANA_DEVNET, + isGivbackEligible: false, + chainType: ChainType.SOLANA, + }; + await Token.create(tokenData as Token).save(); + } + for (const token of SEED_DATA.TOKENS.solana_testnet) { + const tokenData = { + ...token, + networkId: NETWORK_IDS.SOLANA_TESTNET, + isGivbackEligible: false, + chainType: ChainType.SOLANA, + }; + await Token.create(tokenData as Token).save(); + } } async function seedOrganizations() { diff --git a/test/testUtils.ts b/test/testUtils.ts index 36ce13207..91fe51b17 100644 --- a/test/testUtils.ts +++ b/test/testUtils.ts @@ -1366,7 +1366,29 @@ export const SEED_DATA = { decimals: 18, }, ], - solana: [ + solana_mainnet: [ + { + name: 'Solana native token', + symbol: 'SOL', + address: '11111111111111111111111111111111', + decimals: 9, + }, + ], + solana_devnet: [ + { + name: 'Solana native token', + symbol: 'SOL', + address: '11111111111111111111111111111111', + decimals: 9, + }, + { + name: 'TEST-SPL-TOKEN', + symbol: 'TEST-SPL-TOKEN', + address: 'BrEahxkTrCKfjVy36pLD2gvVoMCUMEb1PinrAFtvJqPX', + decimals: 9, + }, + ], + solana_testnet: [ { name: 'Solana native token', symbol: 'SOL', From 4b9d793651a87b52970a0caf764962147d959ead Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 4 Jan 2024 23:47:59 -0500 Subject: [PATCH 02/34] fix solana tests on devnet --- src/resolvers/donationResolver.test.ts | 2 +- src/services/chains/solana/transactionService.ts | 2 +- src/utils/validators/graphqlQueryValidators.ts | 12 ++++++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/resolvers/donationResolver.test.ts b/src/resolvers/donationResolver.test.ts index c815f9a4c..d5641e21d 100644 --- a/src/resolvers/donationResolver.test.ts +++ b/src/resolvers/donationResolver.test.ts @@ -2369,7 +2369,7 @@ function createDonationTestCases() { ); assert.equal( saveDonationResponse.data.errors[0].message, - '"transactionNetworkId" must be one of [1, 3, 5, 100, 137, 10, 420, 56, 42220, 44787, 61, 63, 0]', + '"transactionNetworkId" must be one of [1, 3, 5, 100, 137, 10, 420, 56, 42220, 44787, 61, 63, 101, 102, 103]', ); }); it('should not throw exception when currency is not valid when currency is USDC.e', async () => { diff --git a/src/services/chains/solana/transactionService.ts b/src/services/chains/solana/transactionService.ts index b9184a24b..590c10170 100644 --- a/src/services/chains/solana/transactionService.ts +++ b/src/services/chains/solana/transactionService.ts @@ -33,7 +33,7 @@ const getSolanaWebProvider = (chainId: number) => { } else { // DEVNET solanaProviders[chainId] = new SolanaWeb3.Connection( - process.env.SOLANA_MAINNET_NODE_RPC_URL as string, + process.env.SOLANA_DEVNET_NODE_RPC_URL as string, ); } return solanaProviders[chainId]; diff --git a/src/utils/validators/graphqlQueryValidators.ts b/src/utils/validators/graphqlQueryValidators.ts index bf803d380..c968dc61a 100644 --- a/src/utils/validators/graphqlQueryValidators.ts +++ b/src/utils/validators/graphqlQueryValidators.ts @@ -99,9 +99,17 @@ export const createDonationQueryValidator = Joi.object({ .required() .valid(...Object.values(NETWORK_IDS)), tokenAddress: Joi.when('transactionNetworkId', { - is: 0, // if its solana network + is: NETWORK_IDS.SOLANA_MAINNET, then: Joi.string().pattern(solanaProgramIdRegex), - otherwise: Joi.string().pattern(ethereumWalletAddressRegex), + otherwise: Joi.when('transactionNetworkId', { + is: NETWORK_IDS.SOLANA_DEVNET, + then: Joi.string().pattern(solanaProgramIdRegex), + otherwise: Joi.when('transactionNetworkId', { + is: NETWORK_IDS.SOLANA_TESTNET, + then: Joi.string().pattern(solanaProgramIdRegex), + otherwise: Joi.string().pattern(ethereumWalletAddressRegex), // default case + }), + }), }), token: Joi.string().required(), // .pattern(tokenSymbolRegex) From 461b82332a761addc244c310b35b0a277f0c39dc Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 5 Jan 2024 11:04:42 -0500 Subject: [PATCH 03/34] add solana additional tokens --- migration/1704487070444-addSolanaTokens.ts | 55 ++++++++++++++++ migration/data/seedTokens.ts | 72 +++++++++++++++++++++ src/adapters/price/CoingeckoPriceAdapter.ts | 8 +++ test/testUtils.ts | 48 ++++++++++++++ 4 files changed, 183 insertions(+) create mode 100644 migration/1704487070444-addSolanaTokens.ts diff --git a/migration/1704487070444-addSolanaTokens.ts b/migration/1704487070444-addSolanaTokens.ts new file mode 100644 index 000000000..392fa5a63 --- /dev/null +++ b/migration/1704487070444-addSolanaTokens.ts @@ -0,0 +1,55 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; +import { Token } from '../src/entities/token'; +import seedTokens from './data/seedTokens'; +import { NETWORK_IDS } from '../src/provider'; +import { ChainType } from '../src/types/network'; +import { SOLANA_SYSTEM_PROGRAM } from '../src/utils/networks'; + +export class addSolanaTokens1704487070444 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.manager.save( + Token, + seedTokens.filter( + token => + token.networkId === NETWORK_IDS.SOLANA_MAINNET && + token.chainType === ChainType.SOLANA && + token.address !== SOLANA_SYSTEM_PROGRAM, + ), + ); + const tokens = await queryRunner.query( + `SELECT * FROM token WHERE "chainType" = $1 AND "address" != $2`, + [ChainType.SOLANA, SOLANA_SYSTEM_PROGRAM], + ); + 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) { + await queryRunner.query( + `INSERT INTO organization_tokens_token ("tokenId", "organizationId") VALUES ($1, $2), ($1, $3)`, + [token.id, givethOrganization.id, traceOrganization.id], + ); + } + } + + public async down(queryRunner: QueryRunner): Promise { + const tokens = await queryRunner.query( + `SELECT * FROM token WHERE "chainType" = $1 AND "address" != $2`, + [ChainType.SOLANA, SOLANA_SYSTEM_PROGRAM], + ); + await queryRunner.query( + `DELETE FROM organization_tokens_token WHERE "tokenId" IN (${tokens + .map(token => token.id) + .join(',')})`, + ); + await queryRunner.query( + `DELETE FROM token WHERE "chainType" = $1 AND "address" != $2`, + [ChainType.SOLANA, SOLANA_SYSTEM_PROGRAM], + ); + } +} diff --git a/migration/data/seedTokens.ts b/migration/data/seedTokens.ts index 028632780..aa1276f5e 100644 --- a/migration/data/seedTokens.ts +++ b/migration/data/seedTokens.ts @@ -1259,6 +1259,78 @@ const seedTokens: ITokenData[] = [ chainType: ChainType.SOLANA, coingeckoId: COINGECKO_TOKEN_IDS.SOLANA, }, + { + name: 'Marinade staked SOL', + symbol: 'mSOL', + address: 'mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So', + decimals: 9, + networkId: NETWORK_IDS.SOLANA_MAINNET, + chainType: ChainType.SOLANA, + coingeckoId: COINGECKO_TOKEN_IDS.MSOL, + }, + { + name: 'USDC', + symbol: 'USDC', + address: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', + decimals: 6, + networkId: NETWORK_IDS.SOLANA_MAINNET, + chainType: ChainType.SOLANA, + coingeckoId: COINGECKO_TOKEN_IDS.USDC, + }, + { + name: 'Tether', + symbol: 'USDCT', + address: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', + decimals: 6, + networkId: NETWORK_IDS.SOLANA_MAINNET, + chainType: ChainType.SOLANA, + coingeckoId: COINGECKO_TOKEN_IDS.USDT, + }, + { + name: 'Raydium', + symbol: 'RAY', + address: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', + decimals: 6, + networkId: NETWORK_IDS.SOLANA_MAINNET, + chainType: ChainType.SOLANA, + coingeckoId: COINGECKO_TOKEN_IDS.RAY, + }, + { + name: 'BlazeStake Staked SOL', + symbol: 'BSOL', + address: 'bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1', + decimals: 9, + networkId: NETWORK_IDS.SOLANA_MAINNET, + chainType: ChainType.SOLANA, + coingeckoId: COINGECKO_TOKEN_IDS.BSOL, + }, + { + name: 'Audius (Wormhole)', + symbol: 'AUDIO', + address: '9LzCMqDgTKYz9Drzqnpgee3SGa89up3a247ypMj2xrqM', + decimals: 8, + networkId: NETWORK_IDS.SOLANA_MAINNET, + chainType: ChainType.SOLANA, + coingeckoId: COINGECKO_TOKEN_IDS.AUDIO, + }, + { + name: 'Mango', + symbol: 'MANGO', + address: 'MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac', + decimals: 6, + networkId: NETWORK_IDS.SOLANA_MAINNET, + chainType: ChainType.SOLANA, + coingeckoId: COINGECKO_TOKEN_IDS.MANGO, + }, + { + name: 'Coin98', + symbol: 'C98', + address: 'C98A4nkJXhpVZNAZdHUA95RpTF3T4whtQubL3YobiUX9', + decimals: 6, + networkId: NETWORK_IDS.SOLANA_MAINNET, + chainType: ChainType.SOLANA, + coingeckoId: COINGECKO_TOKEN_IDS.C98, + }, ]; export default seedTokens; diff --git a/src/adapters/price/CoingeckoPriceAdapter.ts b/src/adapters/price/CoingeckoPriceAdapter.ts index 6016e6693..c7f2bae56 100644 --- a/src/adapters/price/CoingeckoPriceAdapter.ts +++ b/src/adapters/price/CoingeckoPriceAdapter.ts @@ -11,6 +11,14 @@ const coingeckoCacheExpirationInSeconds = export const COINGECKO_TOKEN_IDS = { SOLANA: 'solana', + MSOL: 'msol', + USDC: 'usd-coin', + USDT: 'tether', + RAY: 'raydium', + BSOL: 'blazestake-staked-sol', + AUDIO: 'audius-wormhole', + MANGO: 'mango-markets', + C98: 'coin98', }; export class CoingeckoPriceAdapter implements PriceAdapterInterface { diff --git a/test/testUtils.ts b/test/testUtils.ts index 91fe51b17..8f67e73e8 100644 --- a/test/testUtils.ts +++ b/test/testUtils.ts @@ -1373,6 +1373,54 @@ export const SEED_DATA = { address: '11111111111111111111111111111111', decimals: 9, }, + { + name: 'Marinade staked SOL', + symbol: 'mSOL', + address: 'mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So', + decimals: 9, + }, + { + name: 'USDC', + symbol: 'USDC', + address: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', + decimals: 6, + }, + { + name: 'Tether', + symbol: 'USDCT', + address: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', + decimals: 6, + }, + { + name: 'Raydium', + symbol: 'RAY', + address: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', + decimals: 6, + }, + { + name: 'BlazeStake Staked SOL', + symbol: 'BSOL', + address: 'bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1', + decimals: 9, + }, + { + name: 'Audius (Wormhole)', + symbol: 'AUDIO', + address: '9LzCMqDgTKYz9Drzqnpgee3SGa89up3a247ypMj2xrqM', + decimals: 8, + }, + { + name: 'Mango', + symbol: 'MANGO', + address: 'MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac', + decimals: 6, + }, + { + name: 'Coin98', + symbol: 'C98', + address: 'C98A4nkJXhpVZNAZdHUA95RpTF3T4whtQubL3YobiUX9', + decimals: 6, + }, ], solana_devnet: [ { From 3d7d37c4e37760d36fe815aa24ff7f9d93db5c96 Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 5 Jan 2024 16:30:33 -0500 Subject: [PATCH 04/34] fix solana token transfer test --- src/services/chains/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/chains/index.test.ts b/src/services/chains/index.test.ts index 1a8bd34a2..6e22441ca 100644 --- a/src/services/chains/index.test.ts +++ b/src/services/chains/index.test.ts @@ -881,7 +881,7 @@ function getTransactionDetailTestCases() { '5GQGAgGfMNypB5GN4Pp2t3mEMky89bbpZwNDaDh1LJXopVm3bgSxFUgEJ4tEjf2NdibxX4NiiA752Ya2hzg2nqj8', symbol: 'SOL', chainType: ChainType.SOLANA, - networkId: NETWORK_IDS.SOLANA_MAINNET, + networkId: NETWORK_IDS.SOLANA_DEVNET, fromAddress: '5GECDSGSWmMuw6nMfmdBLapa91ZHDZeHqRP1fqvQokjY', toAddress: 'DvWdrYYkwyM9mnTetpr3HBHUBKZ22QdbFEXQ8oquE7Zb', timestamp: 1702931400, From 76ab1d25fcf258640409b769097887bbefdee33b Mon Sep 17 00:00:00 2001 From: Carlos Date: Sun, 7 Jan 2024 15:06:09 -0500 Subject: [PATCH 05/34] improve joi schema chaintype validator --- src/resolvers/donationResolver.ts | 1 + src/utils/validators/graphqlQueryValidators.ts | 16 +++++----------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/resolvers/donationResolver.ts b/src/resolvers/donationResolver.ts index 4c3f7887d..44cebd303 100644 --- a/src/resolvers/donationResolver.ts +++ b/src/resolvers/donationResolver.ts @@ -646,6 +646,7 @@ export class DonationResolver { transakId, referrerId, safeTransactionId, + chainType, }, createDonationQueryValidator, ); diff --git a/src/utils/validators/graphqlQueryValidators.ts b/src/utils/validators/graphqlQueryValidators.ts index c968dc61a..ac3a10caa 100644 --- a/src/utils/validators/graphqlQueryValidators.ts +++ b/src/utils/validators/graphqlQueryValidators.ts @@ -19,6 +19,7 @@ const resourcePerDateRegex = new RegExp( '((?:19|20)\\d\\d)-(0?[1-9]|1[012])-([12][0-9]|3[01]|0?[1-9])', ); +const chainTypeRegex = /^SOLANA$/; const ethereumWalletAddressRegex = /^0x[a-fA-F0-9]{40}$/; const solanaWalletAddressRegex = /^[A-Za-z0-9]{43,44}$/; const solanaProgramIdRegex = @@ -98,18 +99,10 @@ export const createDonationQueryValidator = Joi.object({ transactionNetworkId: Joi.string() .required() .valid(...Object.values(NETWORK_IDS)), - tokenAddress: Joi.when('transactionNetworkId', { - is: NETWORK_IDS.SOLANA_MAINNET, + tokenAddress: Joi.when('chainType', { + is: Joi.string().pattern(chainTypeRegex), then: Joi.string().pattern(solanaProgramIdRegex), - otherwise: Joi.when('transactionNetworkId', { - is: NETWORK_IDS.SOLANA_DEVNET, - then: Joi.string().pattern(solanaProgramIdRegex), - otherwise: Joi.when('transactionNetworkId', { - is: NETWORK_IDS.SOLANA_TESTNET, - then: Joi.string().pattern(solanaProgramIdRegex), - otherwise: Joi.string().pattern(ethereumWalletAddressRegex), // default case - }), - }), + otherwise: Joi.string().pattern(ethereumWalletAddressRegex), // default case }), token: Joi.string().required(), // .pattern(tokenSymbolRegex) @@ -124,6 +117,7 @@ export const createDonationQueryValidator = Joi.object({ transakId: Joi.string(), referrerId: Joi.string().allow(null, ''), safeTransactionId: Joi.string().allow(null, ''), + chainType: Joi.string().required(), }); export const updateDonationQueryValidator = Joi.object({ From 26199f80805b391507f08bd8f2c44e57555d9d28 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Mon, 8 Jan 2024 18:08:26 +0330 Subject: [PATCH 06/34] Add another spl-token transfer test case, change validator of createDonation webservice related to https://github.com/Giveth/impact-graph/pull/1232#discussion_r1444472435 --- src/repositories/donationRepository.test.ts | 4 +- .../qfRoundHistoryRepository.test.ts | 8 +- src/resolvers/donationResolver.test.ts | 199 +++++++++++++---- src/routers/apiGivRoutes.test.ts | 4 +- src/server/adminJs/tabs/tokenTab.test.ts | 4 +- src/services/chains/index.test.ts | 23 +- .../chains/solana/transactionService.ts | 21 +- src/services/donationService.test.ts | 4 +- src/utils/errorMessages.ts | 2 + src/utils/locales/en.json | 210 +++++++++--------- .../validators/graphqlQueryValidators.ts | 13 +- test/testUtils.ts | 29 ++- 12 files changed, 342 insertions(+), 179 deletions(-) diff --git a/src/repositories/donationRepository.test.ts b/src/repositories/donationRepository.test.ts index 5c9acb2af..cb85fc10f 100644 --- a/src/repositories/donationRepository.test.ts +++ b/src/repositories/donationRepository.test.ts @@ -2,7 +2,7 @@ import { createDonationData, createProjectData, generateRandomEtheriumAddress, - generateRandomTxHash, + generateRandomEvmTxHash, graphqlUrl, saveDonationDirectlyToDb, saveProjectDirectlyToDb, @@ -324,7 +324,7 @@ function findDonationsByTransactionIdTestCases() { }); it('should not return donation with invalid txHash ', async () => { const fetchedDonation = await findDonationsByTransactionId( - generateRandomTxHash(), + generateRandomEvmTxHash(), ); assert.isNotOk(fetchedDonation); }); diff --git a/src/repositories/qfRoundHistoryRepository.test.ts b/src/repositories/qfRoundHistoryRepository.test.ts index 3ec2f6de0..c4f46bf7d 100644 --- a/src/repositories/qfRoundHistoryRepository.test.ts +++ b/src/repositories/qfRoundHistoryRepository.test.ts @@ -2,7 +2,7 @@ import { createDonationData, createProjectData, generateRandomEtheriumAddress, - generateRandomTxHash, + generateRandomEvmTxHash, saveDonationDirectlyToDb, saveProjectDirectlyToDb, saveUserDirectlyToDb, @@ -402,7 +402,7 @@ function getQfRoundHistoriesThatDontHaveRelatedDonationsTestCases() { qfRoundId: qfRound.id, }); assert.isNotNull(qfRoundHistory); - qfRoundHistory!.distributedFundTxHash = generateRandomTxHash(); + qfRoundHistory!.distributedFundTxHash = generateRandomEvmTxHash(); qfRoundHistory!.distributedFundNetwork = '100'; qfRoundHistory!.matchingFundAmount = 1000; qfRoundHistory!.matchingFundCurrency = 'DAI'; @@ -470,7 +470,7 @@ function getQfRoundHistoriesThatDontHaveRelatedDonationsTestCases() { qfRoundId: qfRound.id, }); assert.isNotNull(qfRoundHistory); - qfRoundHistory!.distributedFundTxHash = generateRandomTxHash(); + qfRoundHistory!.distributedFundTxHash = generateRandomEvmTxHash(); qfRoundHistory!.distributedFundNetwork = '100'; qfRoundHistory!.matchingFundAmount = 1000; qfRoundHistory!.matchingFundCurrency = 'DAI'; @@ -482,7 +482,7 @@ function getQfRoundHistoriesThatDontHaveRelatedDonationsTestCases() { qfRoundId: qfRound.id, }); assert.isNotNull(qfRoundHistory); - qfRoundHistory2!.distributedFundTxHash = generateRandomTxHash(); + qfRoundHistory2!.distributedFundTxHash = generateRandomEvmTxHash(); qfRoundHistory2!.distributedFundNetwork = '100'; qfRoundHistory2!.matchingFundAmount = 1000; qfRoundHistory2!.matchingFundCurrency = 'DAI'; diff --git a/src/resolvers/donationResolver.test.ts b/src/resolvers/donationResolver.test.ts index d5641e21d..95bc696ce 100644 --- a/src/resolvers/donationResolver.test.ts +++ b/src/resolvers/donationResolver.test.ts @@ -6,13 +6,14 @@ import { DONATION_SEED_DATA, saveProjectDirectlyToDb, createProjectData, - generateRandomTxHash, + generateRandomEvmTxHash, generateRandomEtheriumAddress, saveDonationDirectlyToDb, createDonationData, saveUserDirectlyToDb, generateUserIdLessAccessToken, generateRandomSolanaAddress, + generateRandomSolanaTxHash, } from '../../test/testUtils'; import axios from 'axios'; import { errorMessages } from '../utils/errorMessages'; @@ -678,7 +679,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 1, amount: 10, token: 'GIV', @@ -729,7 +730,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 1, amount: 10, token: 'GIV', @@ -794,7 +795,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 1, amount: 10, token: 'GIV', @@ -839,6 +840,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.SOLANA_MAINNET, + chainType: ChainType.SOLANA, transactionId, nonce: 1, amount: 10, @@ -883,6 +885,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.SOLANA_MAINNET, + chainType: ChainType.SOLANA, transactionId, nonce: 0, amount: 100, @@ -949,7 +952,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 1, amount: 10, token: 'GIV', @@ -979,7 +982,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.CELO, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 1, amount: 10, token: 'GIV', @@ -1045,7 +1048,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 1, amount: 10, token: 'GIV', @@ -1112,7 +1115,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 1, amount: 10, token: 'GIV', @@ -1151,7 +1154,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 1, amount: 10, token: 'GIV', @@ -1187,7 +1190,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 1, amount, token: 'WXDAI', @@ -1224,7 +1227,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.MAIN_NET, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 1, amount, token: 'USDT', @@ -1297,7 +1300,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 1, amount: 10, token: 'GIV', @@ -1337,7 +1340,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.MAIN_NET, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), anonymous: false, nonce: 3, amount: 10, @@ -1376,7 +1379,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.MAIN_NET, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 4, amount: 10, token: 'ABCD', @@ -1414,7 +1417,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.MAIN_NET, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 5, amount: 10, token: 'GIV', @@ -1470,7 +1473,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.MAIN_NET, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 10, amount: 10, token: 'DOGE', @@ -1509,7 +1512,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.MAIN_NET, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), amount: 10, nonce: 11, // custom token @@ -1549,7 +1552,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 12, amount: 10, token: 'GIV', @@ -1587,7 +1590,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 11, amount: 10, token: 'GIV', @@ -1622,7 +1625,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.MAIN_NET, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 13, amount: 10, token: 'GIV', @@ -1654,7 +1657,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.ROPSTEN, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), amount: 10, nonce: 11, token: 'ETH', @@ -1682,7 +1685,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.GOERLI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), amount: 10, nonce: 11, token: 'ETH', @@ -1711,7 +1714,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.MAIN_NET, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 13, amount: 10, token: 'ETH', @@ -1740,7 +1743,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.MAIN_NET, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 14, amount: 10, token: 'DAI', @@ -1772,7 +1775,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), amount: 10, nonce: 13, token: 'XDAI', @@ -1807,7 +1810,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.MAIN_NET, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 15, amount: 10, token: 'ETH', @@ -1834,7 +1837,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 3, amount: 10, token: 'GIV', @@ -1860,7 +1863,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 3, amount: 10, token: 'GIV', @@ -1893,7 +1896,7 @@ function createDonationTestCases() { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, anonymous: true, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 4, amount: 10, token: 'GIV', @@ -1970,7 +1973,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 12, amount: 10, token: 'GIV', @@ -2008,7 +2011,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), amount: 10, nonce: 6, token: 'GIV', @@ -2044,7 +2047,7 @@ function createDonationTestCases() { variables: { projectId: 999999, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 13, amount: 10, token: 'GIV', @@ -2079,7 +2082,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 1, amount: 10, token: 'GIV', @@ -2118,7 +2121,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), amount: 10, nonce: 11, token: 'GIV', @@ -2157,7 +2160,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 12, amount: 10, token: 'GIV', @@ -2192,7 +2195,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), amount: 10, nonce: 14, token: 'GIV', @@ -2227,7 +2230,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 15, amount: 10, token: 'GIV', @@ -2259,7 +2262,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 11, amount: 0, token: 'GIV', @@ -2291,7 +2294,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 11, amount: -10, token: 'GIV', @@ -2355,7 +2358,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: 203, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 11, amount: 10, token: 'GIV', @@ -2387,7 +2390,7 @@ function createDonationTestCases() { variables: { projectId: project.id, transactionNetworkId: NETWORK_IDS.XDAI, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), nonce: 15, amount: 10, token: 'GIV!!', @@ -2407,6 +2410,110 @@ function createDonationTestCases() { }); assert.isOk(donation); }); + it.only('should throw exception when chainType is SOLANA but send EVM tokenAddress', async () => { + const project = await saveProjectDirectlyToDb(createProjectData()); + const user = await User.create({ + walletAddress: generateRandomSolanaAddress(), + loginType: 'wallet', + firstName: 'first name', + }).save(); + const accessToken = await generateTestAccessToken(user.id); + const tokenAddress = generateRandomEtheriumAddress(); + const saveDonationResponse = await axios.post( + graphqlUrl, + { + query: createDonationMutation, + variables: { + projectId: project.id, + transactionNetworkId: NETWORK_IDS.SOLANA_TESTNET, + transactionId: generateRandomSolanaTxHash(), + tokenAddress, + nonce: 11, + amount: 10, + token: 'GIV', + }, + }, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }, + ); + assert.equal( + saveDonationResponse.data.errors[0].message, + errorMessages.INVALID_TOKEN_ADDRESS, + ); + }); + it('should throw exception when chainType is EVM but send SOLANA tokenAddress #1', async () => { + const project = await saveProjectDirectlyToDb(createProjectData()); + const user = await User.create({ + walletAddress: generateRandomEtheriumAddress(), + loginType: 'wallet', + firstName: 'first name', + }).save(); + const accessToken = await generateTestAccessToken(user.id); + const saveDonationResponse = await axios.post( + graphqlUrl, + { + query: createDonationMutation, + variables: { + projectId: project.id, + transactionNetworkId: NETWORK_IDS.XDAI, + transactionId: generateRandomEvmTxHash(), + // SOLANA token address + tokenAddress: '11111111111111111111111111111111', + chainType: ChainType.EVM, + nonce: 11, + amount: 10, + token: 'GIV', + }, + }, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }, + ); + assert.equal( + saveDonationResponse.data.errors[0].message, + errorMessages.INVALID_TOKEN_ADDRESS, + ); + }); + it('should throw exception when chainType is EVM but send SOLANA tokenAddress #2', async () => { + const project = await saveProjectDirectlyToDb(createProjectData()); + const user = await User.create({ + walletAddress: generateRandomEtheriumAddress(), + loginType: 'wallet', + firstName: 'first name', + }).save(); + const accessToken = await generateTestAccessToken(user.id); + const saveDonationResponse = await axios.post( + graphqlUrl, + { + query: createDonationMutation, + variables: { + projectId: project.id, + transactionNetworkId: NETWORK_IDS.XDAI, + transactionId: generateRandomEvmTxHash(), + // SOLANA token address + tokenAddress: generateRandomSolanaAddress(), + chainType: ChainType.EVM, + nonce: 11, + amount: 10, + token: 'GIV', + }, + }, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }, + ); + assert.equal( + saveDonationResponse.data.errors[0].message, + errorMessages.INVALID_TOKEN_ADDRESS, + ); + }); } function donationsFromWalletsTestCases() { @@ -3261,7 +3368,7 @@ function donationsByUserIdTestCases() { const project = await saveProjectDirectlyToDb(projectData); const donationData = { - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), transactionNetworkId: NETWORK_IDS.MAIN_NET, toWalletAddress: generateRandomEtheriumAddress(), fromWalletAddress: generateRandomEtheriumAddress(), @@ -3340,7 +3447,7 @@ function donationsByUserIdTestCases() { const project = await saveProjectDirectlyToDb(projectData); const donationDataAnonymous = { - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), transactionNetworkId: NETWORK_IDS.MAIN_NET, toWalletAddress: SEED_DATA.FIRST_PROJECT.walletAddress, fromWalletAddress: SEED_DATA.FIRST_USER.walletAddress, @@ -3355,7 +3462,7 @@ function donationsByUserIdTestCases() { }; const donationDataNotAnonymous = { - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), transactionNetworkId: NETWORK_IDS.MAIN_NET, toWalletAddress: SEED_DATA.FIRST_PROJECT.walletAddress, fromWalletAddress: SEED_DATA.FIRST_USER.walletAddress, @@ -4028,7 +4135,7 @@ function updateDonationStatusTestCases() { // }); it('should donation status remain pending after calling without sending status (we assume its not mined so far)', async () => { const transactionInfo = { - txHash: generateRandomTxHash(), + txHash: generateRandomEvmTxHash(), networkId: NETWORK_IDS.XDAI, amount: 1, fromAddress: generateRandomEtheriumAddress(), @@ -4198,7 +4305,7 @@ function updateDonationStatusTestCases() { }); it('should update donation status to failed, tx is not mined and donor says it failed', async () => { const transactionInfo = { - txHash: generateRandomTxHash(), + txHash: generateRandomEvmTxHash(), networkId: NETWORK_IDS.XDAI, amount: 1, fromAddress: generateRandomEtheriumAddress(), diff --git a/src/routers/apiGivRoutes.test.ts b/src/routers/apiGivRoutes.test.ts index baa51384b..a8d357b1f 100644 --- a/src/routers/apiGivRoutes.test.ts +++ b/src/routers/apiGivRoutes.test.ts @@ -2,7 +2,7 @@ import { assert } from 'chai'; import { createProjectData, generateRandomEtheriumAddress, - generateRandomTxHash, + generateRandomEvmTxHash, saveProjectDirectlyToDb, } from '../../test/testUtils'; import axios from 'axios'; @@ -26,7 +26,7 @@ describe('createDonation in apiGiv test cases', () => { donationType: 'apiGivProject', status: DONATION_STATUS.VERIFIED, isFiat: false, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), }; const basicAuthentication = createBasicAuthentication({ userName: process.env.API_GIV_USERNAME, diff --git a/src/server/adminJs/tabs/tokenTab.test.ts b/src/server/adminJs/tabs/tokenTab.test.ts index 43c01dfc9..c1095b3ae 100644 --- a/src/server/adminJs/tabs/tokenTab.test.ts +++ b/src/server/adminJs/tabs/tokenTab.test.ts @@ -1,4 +1,4 @@ -import { generateRandomTxHash } from '../../../../test/testUtils'; +import { generateRandomEvmTxHash } from '../../../../test/testUtils'; import { assert } from 'chai'; import { findTokenByTokenAddress } from '../../../repositories/tokenRepository'; import { @@ -114,7 +114,7 @@ function generateOrganizationListTestCases() { }); } -const DRGTTokenAddress = generateRandomTxHash(); +const DRGTTokenAddress = generateRandomEvmTxHash(); function createTokenTestCases() { it('should create token when unique it is unique by network, address', async () => { await createToken( diff --git a/src/services/chains/index.test.ts b/src/services/chains/index.test.ts index 6e22441ca..625634137 100644 --- a/src/services/chains/index.test.ts +++ b/src/services/chains/index.test.ts @@ -873,7 +873,7 @@ function getTransactionDetailTestCases() { // }); /// SOLANA - it('should return transaction detail for SOL transfer on Solana ', async () => { + it('should return transaction detail for SOL transfer on Solana #1', async () => { // https://explorer.solana.com/tx/5GQGAgGfMNypB5GN4Pp2t3mEMky89bbpZwNDaDh1LJXopVm3bgSxFUgEJ4tEjf2NdibxX4NiiA752Ya2hzg2nqj8?cluster=devnet const amount = 0.001; const transactionInfo = await getTransactionInfoFromNetwork({ @@ -934,7 +934,7 @@ function getTransactionDetailTestCases() { ); }); - it('should return transaction detail for spl-token transfer on Solana', async () => { + it('should return transaction detail for spl-token transfer on Solana #1', async () => { // https://solscan.io/tx/2tm14GVsDwXpMzxZzpEWyQnfzcUEv1DZQVQb6VdbsHcV8StoMbBtuQTkW1LJ8RhKKrAL18gbm181NgzuusiQfZ16?cluster=devnet const amount = 7; const transactionInfo = await getTransactionInfoFromNetwork({ @@ -953,6 +953,25 @@ function getTransactionDetailTestCases() { assert.equal(transactionInfo.amount, amount); }); + it('should return transaction detail for spl-token transfer on Solana #2', async () => { + // https://solscan.io/tx/3m6f1g2YK6jtbfVfuYsfDbhVzNAqozF8JJyjp1VuFDduecojqeCVK4htKnLTSk3qBwSqYUvgLpBTVpeLJRvNmeTg?cluster=devnet + const amount = 0.00000005; + const transactionInfo = await getTransactionInfoFromNetwork({ + txHash: + '3m6f1g2YK6jtbfVfuYsfDbhVzNAqozF8JJyjp1VuFDduecojqeCVK4htKnLTSk3qBwSqYUvgLpBTVpeLJRvNmeTg', + symbol: 'TEST-SPL-TOKEN2', + chainType: ChainType.SOLANA, + networkId: NETWORK_IDS.SOLANA_DEVNET, + fromAddress: '26Aks2rN6mfqxdYRXKZbn8CS4GBv6fCMGFYfGWvfFfcx', + toAddress: '7TJgw4hDHh5wdKep3EsBkGMSvtf9LsxdXf89LA48uHoq', + timestamp: 1704699701, + amount, + }); + assert.isOk(transactionInfo); + assert.equal(transactionInfo.currency, 'TEST-SPL-TOKEN2'); + assert.equal(transactionInfo.amount, amount); + }); + it('should return error when transaction time is newer than sent timestamp for spl-token transfer on Solana', async () => { // https://explorer.solana.com/tx/2tm14GVsDwXpMzxZzpEWyQnfzcUEv1DZQVQb6VdbsHcV8StoMbBtuQTkW1LJ8RhKKrAL18gbm181NgzuusiQfZ16?cluster=devnet diff --git a/src/services/chains/solana/transactionService.ts b/src/services/chains/solana/transactionService.ts index 590c10170..dbdd98e5b 100644 --- a/src/services/chains/solana/transactionService.ts +++ b/src/services/chains/solana/transactionService.ts @@ -84,8 +84,6 @@ async function getTransactionDetailForSplTokenTransfer( ): Promise { try { const SPL_TOKEN_TRANSFER_INSTRUCTION_TYPE = 'spl-token'; - const TRANSFER_CHECK_TYPE = 'transferChecked'; - const result = await getSolanaWebProvider( params.networkId, ).getParsedTransaction(params.txHash); @@ -97,17 +95,22 @@ async function getTransactionDetailForSplTokenTransfer( const data = result?.transaction?.message?.instructions?.find( instruction => instruction?.program === SPL_TOKEN_TRANSFER_INSTRUCTION_TYPE && - instruction?.parsed?.type === TRANSFER_CHECK_TYPE && - instruction?.parsed?.info.authority === params.fromAddress && - instruction?.parsed?.info.mint === token?.address, + instruction?.parsed?.info.authority === params.fromAddress, + ); + const toAddressPostBalance = result?.meta?.postTokenBalances?.find( + balance => + balance.owner === params.toAddress && balance.mint === token?.address, ); - const toAddressBalance = result?.meta?.postTokenBalances?.find( + const toAddressPreBalance = result?.meta?.preTokenBalances?.find( balance => balance.owner === params.toAddress && balance.mint === token?.address, ); - if (!data || !toAddressBalance) { + if (!data || !toAddressPostBalance || !toAddressPreBalance) { return null; } + const amount = + toAddressPostBalance.uiTokenAmount?.uiAmount - + toAddressPreBalance.uiTokenAmount?.uiAmount; const parsedData = data as ParsedInstruction; const txInfo = parsedData.parsed.info; @@ -118,8 +121,8 @@ async function getTransactionDetailForSplTokenTransfer( from: txInfo.authority, // we already check toAddressBalance.owner === params.toAddress - to: toAddressBalance?.owner, - amount: txInfo.tokenAmount?.uiAmount, + to: toAddressPostBalance?.owner, + amount, currency: params.symbol, timestamp: result!.blockTime as number, hash: params.txHash, diff --git a/src/services/donationService.test.ts b/src/services/donationService.test.ts index 6aafebc31..c2ccad323 100644 --- a/src/services/donationService.test.ts +++ b/src/services/donationService.test.ts @@ -14,7 +14,7 @@ import { createProjectData, DONATION_SEED_DATA, generateRandomEtheriumAddress, - generateRandomTxHash, + generateRandomEvmTxHash, saveDonationDirectlyToDb, saveProjectDirectlyToDb, saveUserDirectlyToDb, @@ -943,7 +943,7 @@ function insertDonationsFromQfRoundHistoryTestCases() { qfRoundId: qfRound.id, }); assert.isNotNull(qfRoundHistory); - qfRoundHistory!.distributedFundTxHash = generateRandomTxHash(); + qfRoundHistory!.distributedFundTxHash = generateRandomEvmTxHash(); qfRoundHistory!.distributedFundNetwork = '100'; qfRoundHistory!.matchingFundAmount = 1000; qfRoundHistory!.matchingFundCurrency = 'DAI'; diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 5da7238a3..4cee0d598 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -72,6 +72,7 @@ export const errorMessages = { 'This category is not valid', INVALID_TX_HASH: 'Invalid txHash', INVALID_TRANSACTION_ID: 'Invalid transactionId', + INVALID_TOKEN_ADDRESS: 'Invalid tokenAddress', DUPLICATE_TX_HASH: 'There is a donation with this txHash in our DB', YOU_ARE_NOT_THE_OWNER_OF_PROJECT: 'You are not the owner of this project.', YOU_ARE_NOT_THE_OWNER_OF_PROJECT_VERIFICATION_FORM: @@ -231,6 +232,7 @@ export const translationErrorMessagesKeys = { 'CATEGORIES_MUST_BE_FROM_THE_FRONTEND_SUBSELECTION', INVALID_TX_HASH: 'INVALID_TX_HASH', INVALID_TRANSACTION_ID: 'INVALID_TRANSACTION_ID', + INVALID_TOKEN_ADDRESS: 'Invalid tokenAddress', DUPLICATE_TX_HASH: 'DUPLICATE_TX_HASH', YOU_ARE_NOT_THE_OWNER_OF_PROJECT: 'YOU_ARE_NOT_THE_OWNER_OF_PROJECT', YOU_ARE_NOT_THE_OWNER_OF_PROJECT_VERIFICATION_FORM: diff --git a/src/utils/locales/en.json b/src/utils/locales/en.json index df3d91d6e..7f57746b6 100644 --- a/src/utils/locales/en.json +++ b/src/utils/locales/en.json @@ -1,105 +1,107 @@ { - "GITCOIN_ERROR_FETCHING_DATA": "Unable to fetch gitcoin data", - "CHAINVINE_CLICK_EVENT_ERROR": "Unable to register click event or link donor", - "CHAINVINE_REGISTRATION_ERROR": "Chainvine ID failed to be generated for the user", - "FIAT_DONATION_ALREADY_EXISTS": "Fiat donation already exists", - "ONRAMPER_SIGNATURE_INVALID": "Request payload or signature is invalid", - "ONRAMPER_SIGNATURE_MISSING": "Request headers does not contain signature", - "UPLOAD_FAILED": "Upload file failed", - "SPECIFY_GIV_POWER_ADAPTER": "Specify givPower adapter", - "CHANGE_API_INVALID_TITLE_OR_EIN": "ChangeAPI title or EIN not found or invalid", - "INVALID_SOCIAL_NETWORK": "Invalid social network", - "RECIPIENT_ADDRESSES_CANT_BE_EMPTY": "Recipient addresses can't be empty", - "NOT_IMPLEMENTED": "Not implemented", - "SHOULD_SEND_AT_LEAST_ONE_OF_PROJECT_ID_AND_USER_ID": "Should send at least on of userId or projectId", - "YOU_JUST_CAN_VERIFY_REJECTED_AND_SUBMITTED_FORMS": "You just can verify rejected and submitted forms", - "YOU_JUST_CAN_MAKE_DRAFT_REJECTED_AND_SUBMITTED_FORMS": "You just can make draft rejected and submitted forms", - "YOU_JUST_CAN_REJECT_SUBMITTED_FORMS": "You just can reject submitted forms", - "INVALID_TRACK_ID_FOR_OAUTH2_LOGIN": "Invalid trackId for oauth2 login", - "SOCIAL_NETWORK_IS_DIFFERENT_WITH_CLAIMED_ONE": "Social network is different with claimed one", - "SOCIAL_PROFILE_NOT_FOUND": "Social profile not gound", - "CHANGE_API_TITLE_OR_EIN_NOT_PRECISE": "Please query the exact project title or EIN ID from the ChangeAPI site", - "YOU_ARE_NOT_OWNER_OF_THIS_DONATION": "You are not owner of this donation", - "NOT_SUPPORTED_THIRD_PARTY_API": "Third Party API not supported", - "IPFS_IMAGE_UPLOAD_FAILED": "Image upload failed", - "YOU_SHOULD_FILL_EMAIL_PERSONAL_INFO_BEFORE_CONFIRMING_EMAIL": "You should fill email in personal info step before confirming it", - "YOU_ALREADY_VERIFIED_THIS_EMAIL": "You already verified this email", - "INVALID_FROM_DATE": "Invalid fromDate", - "INVALID_TO_DATE": "Invalid toDate", - "VERIFIED_USERNAME_IS_DIFFERENT_WITH_CLAIMED_ONE": "Username is not the claimed one", - "INVALID_AUTHORIZATION_VERSION": "Authorization version is not valid", - "INVALID_STEP": "Invalid step", - "DONOR_REPORTED_IT_AS_FAILED": "Donor reported it as failed", - "INVALID_DATE_FORMAT": "Date format should be YYYYMMDD HH:mm:ss", - "INTERNAL_SERVER_ERROR": "Internal server error", - "ERROR_CONNECTING_DB": "Error in connecting DB", - "YOU_DONT_HAVE_ACCESS_TO_VIEW_THIS_PROJECT": "You dont have access to view this project", - "JUST_ACTIVE_PROJECTS_ACCEPT_DONATION": "Just active projects accept donation", - "CATEGORIES_LENGTH_SHOULD_NOT_BE_MORE_THAN_FIVE": "Please select no more than 5 categories", - "CATEGORIES_MUST_BE_FROM_THE_FRONTEND_SUBSELECTION": "This category is not valid", - "INVALID_TX_HASH": "Invalid txHash", - "INVALID_TRANSACTION_ID": "Invalid transactionId", - "DUPLICATE_TX_HASH": "There is a donation with this txHash in our DB", - "YOU_ARE_NOT_THE_OWNER_OF_PROJECT": "You are not the owner of this project.", - "YOU_ARE_NOT_THE_OWNER_OF_PROJECT_VERIFICATION_FORM": "You are not the owner of this project verification form.", - "YOU_ARE_NOT_THE_OWNER_OF_SOCIAL_PROFILE": "You are not the owner of this social profile project verification form.", - "PROJECT_VERIFICATION_FORM_IS_NOT_DRAFT_SO_YOU_CANT_MODIFY_SOCIAL_PROFILES": "project verification form is not draft, so you cant modify social profiles", - "YOU_ALREADY_ADDED_THIS_SOCIAL_PROFILE_FOR_THIS_VERIFICATION_FORM": "You already have added this social profile for this verification form", - "PROJECT_VERIFICATION_FORM_NOT_FOUND": "Project verification form not found", - "PROJECT_IS_ALREADY_VERIFIED": "Project is already verified.", - "YOU_JUST_CAN_EDIT_DRAFT_REQUESTS": "Project is already verified.", - "EMAIL_CONFIRMATION_CANNOT_BE_SENT_IN_THIS_STEP": "Email confirmation cannot be sent in this step", - "THERE_IS_AN_ONGOING_VERIFICATION_REQUEST_FOR_THIS_PROJECT": "There is an ongoing project verification request for this project", - "THERE_IS_NOT_ANY_ONGOING_PROJECT_VERIFICATION_FORM_FOR_THIS_PROJECT": "There is not any project verification form for this project", - "PROJECT_STATUS_NOT_FOUND": "No project status found, this should be impossible", - "YOU_DONT_HAVE_ACCESS_TO_DEACTIVATE_THIS_PROJECT": "You dont have access to deactivate this project", - "PROJECT_NOT_FOUND": "Project not found.", - "PROJECT_IS_NOT_ACTIVE": "Project is not active.", - "INVALID_FUNCTION": "Invalid function name of transaction", - "PROJECT_UPDATE_NOT_FOUND": "Project update not found.", - "DONATION_NOT_FOUND": "donation not found", - "THIS_PROJECT_IS_CANCELLED_OR_DEACTIVATED_ALREADY": "This project has been cancelled by an Admin for inappropriate content or a violation of the Terms of Use", - "DONATION_VIEWING_LOGIN_REQUIRED": "You must be signed-in in order to register project donations", - "TRANSACTION_NOT_FOUND": "Transaction not found.", - "TRANSACTION_FROM_ADDRESS_IS_DIFFERENT_FROM_SENT_FROM_ADDRESS": "FromAddress of Transaction is different from sent fromAddress", - "TRANSACTION_STATUS_IS_FAILED_IN_NETWORK": "Transaction status is failed in network", - "INVALID_VERIFICATION_REVOKE_STATUS": "Invalid revoke status updated", - "TRANSACTION_NOT_FOUND_AND_NONCE_IS_USED": "Transaction not found and nonce is used", - "TRANSACTION_AMOUNT_IS_DIFFERENT_WITH_SENT_AMOUNT": "Transaction amount is different with sent amount", - "TRANSACTION_CANT_BE_OLDER_THAN_DONATION": "Transaction can not be older than donation", - "TRANSACTION_TO_ADDRESS_IS_DIFFERENT_FROM_SENT_TO_ADDRESS": "ToAddress of Transaction is different to sent toAddress", - "TRANSACTION_SMART_CONTRACT_CONFLICTS_WITH_CURRENCY": "Smart contract address is not equal to transaction.to", - "USER_NOT_FOUND": "User not found.", - "INVALID_NETWORK_ID": "Network Id is invalid", - "INVALID_TOKEN_SYMBOL": "Token symbol is invalid", - "TOKEN_SYMBOL_IS_REQUIRED": "Token symbol is required", - "TOKEN_NOT_FOUND": "Token Not found", - "TRANSACTION_NOT_FOUNT_IN_USER_HISTORY": "TRANSACTION_NOT_FOUNT_IN_USER_HISTORY", - "TRANSACTION_WITH_THIS_NONCE_IS_NOT_MINED_ALREADY": "Transaction with this nonce is not mined already", - "TO_ADDRESS_OF_DONATION_SHOULD_BE_PROJECT_WALLET_ADDRESS": "toAddress of donation should be equal to project wallet address", - "INVALID_WALLET_ADDRESS": "Address not valid", - "INVALID_EMAIL": "Email not valid", - "UN_AUTHORIZED": "unAuthorized", - "BOTH_FIRST_NAME_AND_LAST_NAME_CANT_BE_EMPTY": "Both firstName and lastName cant be empty", - "FIRSTNAME_CANT_BE_EMPTY_STRING": "firstName cant be empty string", - "LASTNAME_CANT_BE_EMPTY_STRING": "lastName cant be empty string", - "PROJECT_WITH_THIS_TITLE_EXISTS": "There is a project with this title, please use another title", - "INVALID_PROJECT_TITLE": "Your project name isnt valid, please only use letters and numbers", - "ACCESS_DENIED": "Access denied", - "AUTHENTICATION_REQUIRED": "Authentication required.", - "SOMETHING_WENT_WRONG": "Something went wrong.", - "PROJECT_DOES_NOT_SUPPORT_THIS_TOKEN": "Project doesnt support this token", - "THERE_IS_NO_RECIPIENT_ADDRESS_FOR_THIS_NETWORK_ID_AND_PROJECT": "There is no recipient address for this project and networkId", - "AMOUNT_IS_INVALID": "Amount is not valid", - "CURRENCY_IS_INVALID": "Currency is not valid", - "SHOULD_HAVE_AT_LEAST_ONE_CONNECTED_SOCIAL_NETWORK_BEFORE_SUBMIT": "Should have one connected social network before submit", - "SOCIAL_PROFILE_IS_ALREADY_VERIFIED": "Social profile is already verified", - "YOU_ARE_NOT_THE_OWNER_OF_THIS_SOCIAL_PROFILE": "You are not the owner of social profile", - "ERROR_IN_GETTING_ACCESS_TOKEN_BY_AUTHORIZATION_CODE": "Error in getting accessToken by authorization code", - "ERROR_GIVPOWER_BOOSTING_FIRST_PROJECT_100_PERCENT": "First project boosting value must be 100%", - "ERROR_GIVPOWER_BOOSTING_INVALID_DATA": "Invalid data", - "ERROR_GIV_POWER_BOOSTING_SUM_IS_GREATER_THAN_MAXIMUM": "Giv power boosting summation is greater than 100", - "ERROR_GIVPOWER_BOOSTING_MAX_PROJECT_LIMIT": "Number of boosted projects exceeds limit", - "REGISTERED_NON_PROFITS_CATEGORY_DOESNT_EXIST": "There is not any category with name registered-non-profits, probably you forgot to run migrations", - "PROJECT_UPDATE_CONTENT_LENGTH_SIZE_EXCEEDED": "Content length exceeded" -} + "GITCOIN_ERROR_FETCHING_DATA": "Unable to fetch gitcoin data", + "CHAINVINE_CLICK_EVENT_ERROR": "Unable to register click event or link donor", + "CHAINVINE_REGISTRATION_ERROR": "Chainvine ID failed to be generated for the user", + "FIAT_DONATION_ALREADY_EXISTS": "Fiat donation already exists", + "ONRAMPER_SIGNATURE_INVALID": "Request payload or signature is invalid", + "ONRAMPER_SIGNATURE_MISSING": "Request headers does not contain signature", + "UPLOAD_FAILED": "Upload file failed", + "SPECIFY_GIV_POWER_ADAPTER": "Specify givPower adapter", + "CHANGE_API_INVALID_TITLE_OR_EIN": "ChangeAPI title or EIN not found or invalid", + "INVALID_SOCIAL_NETWORK": "Invalid social network", + "RECIPIENT_ADDRESSES_CANT_BE_EMPTY": "Recipient addresses can't be empty", + "NOT_IMPLEMENTED": "Not implemented", + "SHOULD_SEND_AT_LEAST_ONE_OF_PROJECT_ID_AND_USER_ID": "Should send at least on of userId or projectId", + "YOU_JUST_CAN_VERIFY_REJECTED_AND_SUBMITTED_FORMS": "You just can verify rejected and submitted forms", + "YOU_JUST_CAN_MAKE_DRAFT_REJECTED_AND_SUBMITTED_FORMS": "You just can make draft rejected and submitted forms", + "YOU_JUST_CAN_REJECT_SUBMITTED_FORMS": "You just can reject submitted forms", + "INVALID_TRACK_ID_FOR_OAUTH2_LOGIN": "Invalid trackId for oauth2 login", + "SOCIAL_NETWORK_IS_DIFFERENT_WITH_CLAIMED_ONE": "Social network is different with claimed one", + "SOCIAL_PROFILE_NOT_FOUND": "Social profile not gound", + "CHANGE_API_TITLE_OR_EIN_NOT_PRECISE": "Please query the exact project title or EIN ID from the ChangeAPI site", + "YOU_ARE_NOT_OWNER_OF_THIS_DONATION": "You are not owner of this donation", + "NOT_SUPPORTED_THIRD_PARTY_API": "Third Party API not supported", + "IPFS_IMAGE_UPLOAD_FAILED": "Image upload failed", + "YOU_SHOULD_FILL_EMAIL_PERSONAL_INFO_BEFORE_CONFIRMING_EMAIL": "You should fill email in personal info step before confirming it", + "YOU_ALREADY_VERIFIED_THIS_EMAIL": "You already verified this email", + "INVALID_FROM_DATE": "Invalid fromDate", + "INVALID_TO_DATE": "Invalid toDate", + "VERIFIED_USERNAME_IS_DIFFERENT_WITH_CLAIMED_ONE": "Username is not the claimed one", + "INVALID_AUTHORIZATION_VERSION": "Authorization version is not valid", + "INVALID_STEP": "Invalid step", + "DONOR_REPORTED_IT_AS_FAILED": "Donor reported it as failed", + "INVALID_DATE_FORMAT": "Date format should be YYYYMMDD HH:mm:ss", + "INTERNAL_SERVER_ERROR": "Internal server error", + "ERROR_CONNECTING_DB": "Error in connecting DB", + "YOU_DONT_HAVE_ACCESS_TO_VIEW_THIS_PROJECT": "You dont have access to view this project", + "JUST_ACTIVE_PROJECTS_ACCEPT_DONATION": "Just active projects accept donation", + "CATEGORIES_LENGTH_SHOULD_NOT_BE_MORE_THAN_FIVE": "Please select no more than 5 categories", + "CATEGORIES_MUST_BE_FROM_THE_FRONTEND_SUBSELECTION": "This category is not valid", + "INVALID_TX_HASH": "Invalid txHash", + "INVALID_TRANSACTION_ID": "Invalid transactionId", + "DUPLICATE_TX_HASH": "There is a donation with this txHash in our DB", + "YOU_ARE_NOT_THE_OWNER_OF_PROJECT": "You are not the owner of this project.", + "YOU_ARE_NOT_THE_OWNER_OF_PROJECT_VERIFICATION_FORM": "You are not the owner of this project verification form.", + "YOU_ARE_NOT_THE_OWNER_OF_SOCIAL_PROFILE": "You are not the owner of this social profile project verification form.", + "PROJECT_VERIFICATION_FORM_IS_NOT_DRAFT_SO_YOU_CANT_MODIFY_SOCIAL_PROFILES": "project verification form is not draft, so you cant modify social profiles", + "YOU_ALREADY_ADDED_THIS_SOCIAL_PROFILE_FOR_THIS_VERIFICATION_FORM": "You already have added this social profile for this verification form", + "PROJECT_VERIFICATION_FORM_NOT_FOUND": "Project verification form not found", + "PROJECT_IS_ALREADY_VERIFIED": "Project is already verified.", + "YOU_JUST_CAN_EDIT_DRAFT_REQUESTS": "Project is already verified.", + "EMAIL_CONFIRMATION_CANNOT_BE_SENT_IN_THIS_STEP": "Email confirmation cannot be sent in this step", + "THERE_IS_AN_ONGOING_VERIFICATION_REQUEST_FOR_THIS_PROJECT": "There is an ongoing project verification request for this project", + "THERE_IS_NOT_ANY_ONGOING_PROJECT_VERIFICATION_FORM_FOR_THIS_PROJECT": "There is not any project verification form for this project", + "PROJECT_STATUS_NOT_FOUND": "No project status found, this should be impossible", + "YOU_DONT_HAVE_ACCESS_TO_DEACTIVATE_THIS_PROJECT": "You dont have access to deactivate this project", + "PROJECT_NOT_FOUND": "Project not found.", + "PROJECT_IS_NOT_ACTIVE": "Project is not active.", + "INVALID_FUNCTION": "Invalid function name of transaction", + "PROJECT_UPDATE_NOT_FOUND": "Project update not found.", + "DONATION_NOT_FOUND": "donation not found", + "THIS_PROJECT_IS_CANCELLED_OR_DEACTIVATED_ALREADY": "This project has been cancelled by an Admin for inappropriate content or a violation of the Terms of Use", + "DONATION_VIEWING_LOGIN_REQUIRED": "You must be signed-in in order to register project donations", + "TRANSACTION_NOT_FOUND": "Transaction not found.", + "TRANSACTION_FROM_ADDRESS_IS_DIFFERENT_FROM_SENT_FROM_ADDRESS": "FromAddress of Transaction is different from sent fromAddress", + "TRANSACTION_STATUS_IS_FAILED_IN_NETWORK": "Transaction status is failed in network", + "INVALID_VERIFICATION_REVOKE_STATUS": "Invalid revoke status updated", + "TRANSACTION_NOT_FOUND_AND_NONCE_IS_USED": "Transaction not found and nonce is used", + "TRANSACTION_AMOUNT_IS_DIFFERENT_WITH_SENT_AMOUNT": "Transaction amount is different with sent amount", + "TRANSACTION_CANT_BE_OLDER_THAN_DONATION": "Transaction can not be older than donation", + "TRANSACTION_TO_ADDRESS_IS_DIFFERENT_FROM_SENT_TO_ADDRESS": "ToAddress of Transaction is different to sent toAddress", + "TRANSACTION_SMART_CONTRACT_CONFLICTS_WITH_CURRENCY": "Smart contract address is not equal to transaction.to", + "USER_NOT_FOUND": "User not found.", + "INVALID_NETWORK_ID": "Network Id is invalid", + "INVALID_TOKEN_SYMBOL": "Token symbol is invalid", + "TOKEN_SYMBOL_IS_REQUIRED": "Token symbol is required", + "TOKEN_NOT_FOUND": "Token Not found", + "TRANSACTION_NOT_FOUNT_IN_USER_HISTORY": "TRANSACTION_NOT_FOUNT_IN_USER_HISTORY", + "TRANSACTION_WITH_THIS_NONCE_IS_NOT_MINED_ALREADY": "Transaction with this nonce is not mined already", + "TO_ADDRESS_OF_DONATION_SHOULD_BE_PROJECT_WALLET_ADDRESS": "toAddress of donation should be equal to project wallet address", + "INVALID_WALLET_ADDRESS": "Address not valid", + "INVALID_EMAIL": "Email not valid", + "UN_AUTHORIZED": "unAuthorized", + "BOTH_FIRST_NAME_AND_LAST_NAME_CANT_BE_EMPTY": "Both firstName and lastName cant be empty", + "FIRSTNAME_CANT_BE_EMPTY_STRING": "firstName cant be empty string", + "LASTNAME_CANT_BE_EMPTY_STRING": "lastName cant be empty string", + "PROJECT_WITH_THIS_TITLE_EXISTS": "There is a project with this title, please use another title", + "INVALID_PROJECT_TITLE": "Your project name isnt valid, please only use letters and numbers", + "ACCESS_DENIED": "Access denied", + "AUTHENTICATION_REQUIRED": "Authentication required.", + "SOMETHING_WENT_WRONG": "Something went wrong.", + "PROJECT_DOES_NOT_SUPPORT_THIS_TOKEN": "Project doesnt support this token", + "THERE_IS_NO_RECIPIENT_ADDRESS_FOR_THIS_NETWORK_ID_AND_PROJECT": "There is no recipient address for this project and networkId", + "AMOUNT_IS_INVALID": "Amount is not valid", + "CURRENCY_IS_INVALID": "Currency is not valid", + "SHOULD_HAVE_AT_LEAST_ONE_CONNECTED_SOCIAL_NETWORK_BEFORE_SUBMIT": "Should have one connected social network before submit", + "SOCIAL_PROFILE_IS_ALREADY_VERIFIED": "Social profile is already verified", + "YOU_ARE_NOT_THE_OWNER_OF_THIS_SOCIAL_PROFILE": "You are not the owner of social profile", + "ERROR_IN_GETTING_ACCESS_TOKEN_BY_AUTHORIZATION_CODE": "Error in getting accessToken by authorization code", + "ERROR_GIVPOWER_BOOSTING_FIRST_PROJECT_100_PERCENT": "First project boosting value must be 100%", + "ERROR_GIVPOWER_BOOSTING_INVALID_DATA": "Invalid data", + "ERROR_GIV_POWER_BOOSTING_SUM_IS_GREATER_THAN_MAXIMUM": "Giv power boosting summation is greater than 100", + "ERROR_GIVPOWER_BOOSTING_MAX_PROJECT_LIMIT": "Number of boosted projects exceeds limit", + "REGISTERED_NON_PROFITS_CATEGORY_DOESNT_EXIST": "There is not any category with name registered-non-profits, probably you forgot to run migrations", + "PROJECT_UPDATE_CONTENT_LENGTH_SIZE_EXCEEDED": "Content length exceeded", + "INVALID_TOKEN_ADDRESS": "INVALID_TOKEN_ADDRESS", + "Invalid tokenAddress": "Invalid tokenAddress" +} \ No newline at end of file diff --git a/src/utils/validators/graphqlQueryValidators.ts b/src/utils/validators/graphqlQueryValidators.ts index ac3a10caa..edd52ca1c 100644 --- a/src/utils/validators/graphqlQueryValidators.ts +++ b/src/utils/validators/graphqlQueryValidators.ts @@ -19,7 +19,6 @@ const resourcePerDateRegex = new RegExp( '((?:19|20)\\d\\d)-(0?[1-9]|1[012])-([12][0-9]|3[01]|0?[1-9])', ); -const chainTypeRegex = /^SOLANA$/; const ethereumWalletAddressRegex = /^0x[a-fA-F0-9]{40}$/; const solanaWalletAddressRegex = /^[A-Za-z0-9]{43,44}$/; const solanaProgramIdRegex = @@ -99,10 +98,18 @@ export const createDonationQueryValidator = Joi.object({ transactionNetworkId: Joi.string() .required() .valid(...Object.values(NETWORK_IDS)), + tokenAddress: Joi.when('chainType', { - is: Joi.string().pattern(chainTypeRegex), + is: ChainType.SOLANA, then: Joi.string().pattern(solanaProgramIdRegex), - otherwise: Joi.string().pattern(ethereumWalletAddressRegex), // default case + otherwise: Joi.string().pattern(ethereumWalletAddressRegex), + }).messages({ + 'string.pattern.base': i18n.__( + translationErrorMessagesKeys.INVALID_TOKEN_ADDRESS, + ), + 'string.disallow': i18n.__( + translationErrorMessagesKeys.INVALID_TOKEN_ADDRESS, + ), }), token: Joi.string().required(), // .pattern(tokenSymbolRegex) diff --git a/test/testUtils.ts b/test/testUtils.ts index 8f67e73e8..83b5a0435 100644 --- a/test/testUtils.ts +++ b/test/testUtils.ts @@ -303,7 +303,7 @@ export const createDonationData = (params?: { qfRoundId?: number; }): CreateDonationData => { return { - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), transactionNetworkId: NETWORK_IDS.MAIN_NET, toWalletAddress: SEED_DATA.FIRST_PROJECT.walletAddress, fromWalletAddress: SEED_DATA.FIRST_USER.walletAddress, @@ -1435,6 +1435,12 @@ export const SEED_DATA = { address: 'BrEahxkTrCKfjVy36pLD2gvVoMCUMEb1PinrAFtvJqPX', decimals: 9, }, + { + name: 'TEST-SPL-TOKEN2', + symbol: 'TEST-SPL-TOKEN2', + address: '8LDBhHJB7oMAjkJaetXa4njjetUVWDRTqvzkmhFQjgeK', + decimals: 9, + }, ], solana_testnet: [ { @@ -1696,7 +1702,7 @@ export const REACTION_SEED_DATA = { export const DONATION_SEED_DATA = { FIRST_DONATION: { id: 1, - transactionId: generateRandomTxHash(), + transactionId: generateRandomEvmTxHash(), transactionNetworkId: NETWORK_IDS.MAIN_NET, toWalletAddress: SEED_DATA.FIRST_PROJECT.walletAddress, fromWalletAddress: SEED_DATA.FIRST_USER.walletAddress, @@ -1845,7 +1851,7 @@ export function generateRandomSolanaAddress(): string { return Keypair.generate().publicKey.toString(); } -export function generateRandomTxHash(): string { +export function generateRandomEvmTxHash(): string { return `0x${generateHexNumber(64)}`; } @@ -1858,3 +1864,20 @@ export function generateHexNumber(len): string { } return output; } + +function generateRandomAlphanumeric(length) { + let result = ''; + const characters = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + const charactersLength = characters.length; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} + +export function generateRandomSolanaTxHash() { + // Random length between 86 and 88 + const length = Math.floor(Math.random() * 3) + 86; + return generateRandomAlphanumeric(length); +} From 7adfc7c71237e6d9c721027a8a5240604f4d59aa Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Mon, 8 Jan 2024 18:13:56 +0330 Subject: [PATCH 07/34] Refactor and change a nested if-else with witch case --- .../chains/solana/transactionService.ts | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/services/chains/solana/transactionService.ts b/src/services/chains/solana/transactionService.ts index dbdd98e5b..4908928ff 100644 --- a/src/services/chains/solana/transactionService.ts +++ b/src/services/chains/solana/transactionService.ts @@ -22,19 +22,22 @@ const getSolanaWebProvider = (chainId: number) => { if (solanaProviders.has(chainId)) { return solanaProviders.get(chainId); } - if (chainId === NETWORK_IDS.SOLANA_MAINNET) { - solanaProviders[chainId] = new SolanaWeb3.Connection( - process.env.SOLANA_MAINNET_NODE_RPC_URL as string, - ); - } else if (chainId === NETWORK_IDS.SOLANA_TESTNET) { - solanaProviders[chainId] = new SolanaWeb3.Connection( - process.env.SOLANA_TEST_NODE_RPC_URL as string, - ); - } else { - // DEVNET - solanaProviders[chainId] = new SolanaWeb3.Connection( - process.env.SOLANA_DEVNET_NODE_RPC_URL as string, - ); + switch (chainId) { + case NETWORK_IDS.SOLANA_MAINNET: + solanaProviders[chainId] = new SolanaWeb3.Connection( + process.env.SOLANA_MAINNET_NODE_RPC_URL as string, + ); + break; + case NETWORK_IDS.SOLANA_TESTNET: + solanaProviders[chainId] = new SolanaWeb3.Connection( + process.env.SOLANA_TEST_NODE_RPC_URL as string, + ); + break; + default: + // DEVNET + solanaProviders[chainId] = new SolanaWeb3.Connection( + process.env.SOLANA_DEVNET_NODE_RPC_URL as string, + ); } return solanaProviders[chainId]; }; From 212279f30f7d2791d81b273265bd016725c51107 Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Tue, 9 Jan 2024 15:55:47 +0330 Subject: [PATCH 08/34] Removed only tags from tests --- src/resolvers/donationResolver.test.ts | 2 +- src/utils/networks.test.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 src/utils/networks.test.ts diff --git a/src/resolvers/donationResolver.test.ts b/src/resolvers/donationResolver.test.ts index 95bc696ce..bd8e22202 100644 --- a/src/resolvers/donationResolver.test.ts +++ b/src/resolvers/donationResolver.test.ts @@ -2410,7 +2410,7 @@ function createDonationTestCases() { }); assert.isOk(donation); }); - it.only('should throw exception when chainType is SOLANA but send EVM tokenAddress', async () => { + it('should throw exception when chainType is SOLANA but send EVM tokenAddress', async () => { const project = await saveProjectDirectlyToDb(createProjectData()); const user = await User.create({ walletAddress: generateRandomSolanaAddress(), diff --git a/src/utils/networks.test.ts b/src/utils/networks.test.ts new file mode 100644 index 000000000..17693c18b --- /dev/null +++ b/src/utils/networks.test.ts @@ -0,0 +1,12 @@ +import { assert } from 'chai'; +import { detectAddressChainType } from './networks'; +import { ChainType } from '../types/network'; + +describe('networks - ' + detectAddressChainType.name, () => { + it('detect solana address - 1', () => { + assert.equal( + detectAddressChainType('GEhUKKZeENY1TmaavqvLJ5GbbQs9GkzECFSE2bpjzz3k'), + ChainType.SOLANA, + ); + }); +}); From ed61794b1f3429fb76175f5a73c25dd654982fdb Mon Sep 17 00:00:00 2001 From: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> Date: Wed, 10 Jan 2024 08:45:04 -0500 Subject: [PATCH 09/34] Update src/utils/errorMessages.ts Co-authored-by: Amin Latifi --- src/utils/errorMessages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 4cee0d598..196afb6ad 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -232,7 +232,7 @@ export const translationErrorMessagesKeys = { 'CATEGORIES_MUST_BE_FROM_THE_FRONTEND_SUBSELECTION', INVALID_TX_HASH: 'INVALID_TX_HASH', INVALID_TRANSACTION_ID: 'INVALID_TRANSACTION_ID', - INVALID_TOKEN_ADDRESS: 'Invalid tokenAddress', + INVALID_TOKEN_ADDRESS: 'INVALID_TOKEN_ADDRESS', DUPLICATE_TX_HASH: 'DUPLICATE_TX_HASH', YOU_ARE_NOT_THE_OWNER_OF_PROJECT: 'YOU_ARE_NOT_THE_OWNER_OF_PROJECT', YOU_ARE_NOT_THE_OWNER_OF_PROJECT_VERIFICATION_FORM: From 4ac1c902cdf7072625c13333b79ec6e013b5308e Mon Sep 17 00:00:00 2001 From: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> Date: Wed, 10 Jan 2024 08:45:49 -0500 Subject: [PATCH 10/34] Update src/utils/locales/en.json Co-authored-by: Amin Latifi --- src/utils/locales/en.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/utils/locales/en.json b/src/utils/locales/en.json index 7f57746b6..dddced435 100644 --- a/src/utils/locales/en.json +++ b/src/utils/locales/en.json @@ -102,6 +102,5 @@ "ERROR_GIVPOWER_BOOSTING_MAX_PROJECT_LIMIT": "Number of boosted projects exceeds limit", "REGISTERED_NON_PROFITS_CATEGORY_DOESNT_EXIST": "There is not any category with name registered-non-profits, probably you forgot to run migrations", "PROJECT_UPDATE_CONTENT_LENGTH_SIZE_EXCEEDED": "Content length exceeded", - "INVALID_TOKEN_ADDRESS": "INVALID_TOKEN_ADDRESS", - "Invalid tokenAddress": "Invalid tokenAddress" + "INVALID_TOKEN_ADDRESS": "Invalid tokenAddress" } \ No newline at end of file From 761015faca1b7fd2156650c1c627da78bde4fbca Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Mon, 15 Jan 2024 16:52:55 +0330 Subject: [PATCH 11/34] Fill networkId for solana addresses when create/update projects --- src/repositories/projectRepository.ts | 6 +++++- .../projectVerificationRepository.ts | 10 +++++++++ src/resolvers/projectResolver.ts | 15 ++++++------- .../projectVerificationFormResolver.test.ts | 21 +++++++++++++++++-- src/services/chains/index.ts | 5 +++++ .../validators/graphqlQueryValidators.ts | 4 ++++ test/graphqlQueries.ts | 1 + 7 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/repositories/projectRepository.ts b/src/repositories/projectRepository.ts index 1cd9d755e..da963f7af 100644 --- a/src/repositories/projectRepository.ts +++ b/src/repositories/projectRepository.ts @@ -13,6 +13,8 @@ import { i18n, translationErrorMessagesKeys } from '../utils/errorMessages'; import { User, publicSelectionFields } from '../entities/user'; import { ResourcesTotalPerMonthAndYear } from '../resolvers/donationResolver'; import { OrderDirection, ProjectResolver } from '../resolvers/projectResolver'; +import { ChainType } from '../types/network'; +import { getDefaultSolanaChainId } from '../services/chains'; export const findProjectById = (projectId: number): Promise => { // return Project.findOne({ id: projectId }); @@ -296,7 +298,9 @@ export const updateProjectWithVerificationForm = async ( await ProjectAddress.create({ title: relatedAddress.title, address: relatedAddress.address, - networkId: relatedAddress.networkId, + + // Frontend doesn't send networkId for solana addresses so we set it to default solana chain id + networkId: relatedAddress.networkId || getDefaultSolanaChainId(), projectId: verificationForm.projectId, userId: verificationForm.user?.id, project, diff --git a/src/repositories/projectVerificationRepository.ts b/src/repositories/projectVerificationRepository.ts index 45d8a58f1..14aa3b2bf 100644 --- a/src/repositories/projectVerificationRepository.ts +++ b/src/repositories/projectVerificationRepository.ts @@ -17,6 +17,8 @@ import { translationErrorMessagesKeys, } from '../utils/errorMessages'; import { User } from '../entities/user'; +import { getDefaultSolanaChainId } from '../services/chains'; +import { add } from 'lodash'; export const createProjectVerificationForm = async (params: { userId: number; @@ -304,6 +306,14 @@ export const updateManagingFundsOfProjectVerification = async (params: { i18n.__(translationErrorMessagesKeys.PROJECT_VERIFICATION_FORM_NOT_FOUND), ); } + + managingFunds.relatedAddresses = managingFunds.relatedAddresses.map( + address => { + // because frontend sends 0 for solana addresses + address.networkId = address.networkId || getDefaultSolanaChainId(); + return address; + }, + ); projectVerificationForm.managingFunds = managingFunds; return projectVerificationForm?.save(); }; diff --git a/src/resolvers/projectResolver.ts b/src/resolvers/projectResolver.ts index b21ed5f69..b6bdbf285 100644 --- a/src/resolvers/projectResolver.ts +++ b/src/resolvers/projectResolver.ts @@ -36,7 +36,6 @@ import { Int, Mutation, ObjectType, - PubSub, Query, registerEnumType, Resolver, @@ -101,18 +100,18 @@ import { AppDataSource } from '../orm'; import { creteSlugFromProject } from '../utils/utils'; import { findCampaignBySlug } from '../repositories/campaignRepository'; import { Campaign } from '../entities/campaign'; - -const projectFiltersCacheDuration = Number( - process.env.PROJECT_FILTERS_THREADS_POOL_DURATION || 60000, -); import { FeaturedUpdate } from '../entities/featuredUpdate'; import { PROJECT_UPDATE_CONTENT_MAX_LENGTH } from '../constants/validators'; import { calculateGivbackFactor } from '../services/givbackService'; import { ProjectBySlugResponse } from './types/projectResolver'; import { ChainType } from '../types/network'; -import { detectAddressChainType } from '../utils/networks'; import { findActiveQfRound } from '../repositories/qfRoundRepository'; import { getAllProjectsRelatedToActiveCampaigns } from '../services/campaignService'; +import { getDefaultSolanaChainId } from '../services/chains'; + +const projectFiltersCacheDuration = Number( + process.env.PROJECT_FILTERS_THREADS_POOL_DURATION || 60000, +); @ObjectType() class AllProjects { @@ -1043,7 +1042,9 @@ export class ProjectResolver { user: adminUser, address: relatedAddress.address, chainType: relatedAddress.chainType, - networkId: relatedAddress.networkId, + + // Frontend doesn't send networkId for solana addresses so we set it to default solana chain id + networkId: relatedAddress.networkId || getDefaultSolanaChainId(), isRecipient: true, }; }), diff --git a/src/resolvers/projectVerificationFormResolver.test.ts b/src/resolvers/projectVerificationFormResolver.test.ts index ae2c23546..a5836faf3 100644 --- a/src/resolvers/projectVerificationFormResolver.test.ts +++ b/src/resolvers/projectVerificationFormResolver.test.ts @@ -22,12 +22,12 @@ import { import { ManagingFunds, Milestones, + PersonalInfo, PROJECT_VERIFICATION_STATUSES, PROJECT_VERIFICATION_STEPS, ProjectContacts, ProjectRegistry, ProjectVerificationForm, - PersonalInfo, } from '../entities/projectVerificationForm'; import { Project, ProjStatus, ReviewStatus } from '../entities/project'; import { @@ -40,6 +40,7 @@ import { countriesList, generateRandomString } from '../utils/utils'; import { createSocialProfile } from '../repositories/socialProfileRepository'; import { SOCIAL_NETWORKS } from '../entities/socialProfile'; import { ChainType } from '../types/network'; +import { getDefaultSolanaChainId } from '../services/chains'; describe( 'createProjectVerification test cases', @@ -322,9 +323,16 @@ function updateProjectVerificationFormMutationTestCases() { title: 'test title', chainType: ChainType.EVM, }, + // { + // address: generateRandomSolanaAddress(), + // networkId: NETWORK_IDS.SOLANA_MAINNET, + // title: 'test title', + // chainType: ChainType.SOLANA, + // }, { address: generateRandomSolanaAddress(), - networkId: NETWORK_IDS.SOLANA_MAINNET, + // frontend may not send networkId for solana + networkId: 0, title: 'test title', chainType: ChainType.SOLANA, }, @@ -743,6 +751,15 @@ function updateProjectVerificationFormMutationTestCases() { .relatedAddresses[0].address, managingFunds.relatedAddresses[0].address, ); + + // Make sure that networkId would be getDefaultSolanaChainId() instead of 0 + assert.equal( + result.data.data.updateProjectVerificationForm.managingFunds.relatedAddresses.find( + address => address.chainType === ChainType.SOLANA, + ).networkId, + getDefaultSolanaChainId(), + ); + assert.equal( result.data.data.updateProjectVerificationForm.lastStep, PROJECT_VERIFICATION_STEPS.MANAGING_FUNDS, diff --git a/src/services/chains/index.ts b/src/services/chains/index.ts index 5e8b75af7..41426df24 100644 --- a/src/services/chains/index.ts +++ b/src/services/chains/index.ts @@ -3,6 +3,7 @@ import { getSolanaTransactionInfoFromNetwork } from './solana/transactionService import { getEvmTransactionInfoFromNetwork } from './evm/transactionService'; import { i18n, translationErrorMessagesKeys } from '../../utils/errorMessages'; import { logger } from '../../utils/logger'; +import { NETWORK_IDS } from '../../provider'; export interface NetworkTransactionInfo { hash: string; @@ -85,3 +86,7 @@ export async function getTransactionInfoFromNetwork( // If chain is not Solana, it's EVM for sure return getEvmTransactionInfoFromNetwork(input); } + +export function getDefaultSolanaChainId(): number { + return Number(process.env.SOLANA_CHAIN_ID) || NETWORK_IDS.SOLANA_DEVNET; +} diff --git a/src/utils/validators/graphqlQueryValidators.ts b/src/utils/validators/graphqlQueryValidators.ts index edd52ca1c..9b9a2569a 100644 --- a/src/utils/validators/graphqlQueryValidators.ts +++ b/src/utils/validators/graphqlQueryValidators.ts @@ -186,7 +186,11 @@ const managingFundsValidator = Joi.object({ Joi.string().required().pattern(solanaWalletAddressRegex), ), networkId: Joi.number()?.valid( + 0, // frontend may send 0 as a network id for solana, so we should allow it NETWORK_IDS.SOLANA_MAINNET, // Solana + NETWORK_IDS.SOLANA_DEVNET, + NETWORK_IDS.SOLANA_TESTNET, + NETWORK_IDS.MAIN_NET, NETWORK_IDS.ROPSTEN, NETWORK_IDS.GOERLI, diff --git a/test/graphqlQueries.ts b/test/graphqlQueries.ts index 131084d52..842fe9a26 100644 --- a/test/graphqlQueries.ts +++ b/test/graphqlQueries.ts @@ -1799,6 +1799,7 @@ export const updateProjectVerificationFormMutation = ` managingFunds { description relatedAddresses { + chainType address networkId title From 311d0b13ebbe6c2395b95f4b1df50fc606713847 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Mon, 15 Jan 2024 17:04:41 +0330 Subject: [PATCH 12/34] Add some test tokens for solana dev chain --- migration/1703044586989-addSolanaToken.ts | 26 ++++++++++------ migration/1704487070444-addSolanaTokens.ts | 23 +++++++++----- migration/data/seedTokens.ts | 35 +++++++++++++++++++++- 3 files changed, 66 insertions(+), 18 deletions(-) diff --git a/migration/1703044586989-addSolanaToken.ts b/migration/1703044586989-addSolanaToken.ts index 7ae919879..c21ca9d4a 100644 --- a/migration/1703044586989-addSolanaToken.ts +++ b/migration/1703044586989-addSolanaToken.ts @@ -4,17 +4,25 @@ import { Token } from '../src/entities/token'; import seedTokens from './data/seedTokens'; import { ChainType } from '../src/types/network'; import { SOLANA_SYSTEM_PROGRAM } from '../src/utils/networks'; +import { ENVIRONMENTS } from '../src/utils/utils'; export class addSolanaToken1703044586989 implements MigrationInterface { - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.manager.save( - Token, - seedTokens.filter( + async up(queryRunner: QueryRunner): Promise { + let tokensData; + if (process.env.ENVIRONMENT === ENVIRONMENTS.PRODUCTION) { + tokensData = seedTokens.filter( token => - token.address === SOLANA_SYSTEM_PROGRAM && - token.chainType === ChainType.SOLANA, - ), - ); + token.networkId === NETWORK_IDS.SOLANA_MAINNET && + token.address === SOLANA_SYSTEM_PROGRAM, + ); + } else { + tokensData = seedTokens.filter( + token => + token.networkId === NETWORK_IDS.SOLANA_DEVNET && + token.address === SOLANA_SYSTEM_PROGRAM, + ); + } + await queryRunner.manager.save(Token, tokensData); const tokens = await queryRunner.query( `SELECT * FROM token WHERE "chainType" = $1 AND "address" = $2`, [ChainType.SOLANA, SOLANA_SYSTEM_PROGRAM], @@ -33,7 +41,7 @@ export class addSolanaToken1703044586989 implements MigrationInterface { ); } - public async down(queryRunner: QueryRunner): Promise { + async down(queryRunner: QueryRunner): Promise { const tokens = await queryRunner.query( `SELECT * FROM token WHERE "chainType" = $1 AND "address" = $2`, [ChainType.SOLANA, SOLANA_SYSTEM_PROGRAM], diff --git a/migration/1704487070444-addSolanaTokens.ts b/migration/1704487070444-addSolanaTokens.ts index 392fa5a63..2e257c435 100644 --- a/migration/1704487070444-addSolanaTokens.ts +++ b/migration/1704487070444-addSolanaTokens.ts @@ -4,18 +4,25 @@ import seedTokens from './data/seedTokens'; import { NETWORK_IDS } from '../src/provider'; import { ChainType } from '../src/types/network'; import { SOLANA_SYSTEM_PROGRAM } from '../src/utils/networks'; +import { ENVIRONMENTS } from '../src/utils/utils'; export class addSolanaTokens1704487070444 implements MigrationInterface { - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.manager.save( - Token, - seedTokens.filter( + async up(queryRunner: QueryRunner): Promise { + let tokensData; + if (process.env.ENVIRONMENT === ENVIRONMENTS.PRODUCTION) { + tokensData = seedTokens.filter( token => token.networkId === NETWORK_IDS.SOLANA_MAINNET && - token.chainType === ChainType.SOLANA && token.address !== SOLANA_SYSTEM_PROGRAM, - ), - ); + ); + } else { + tokensData = seedTokens.filter( + token => + token.networkId === NETWORK_IDS.SOLANA_DEVNET && + token.address !== SOLANA_SYSTEM_PROGRAM, + ); + } + await queryRunner.manager.save(Token, tokensData); const tokens = await queryRunner.query( `SELECT * FROM token WHERE "chainType" = $1 AND "address" != $2`, [ChainType.SOLANA, SOLANA_SYSTEM_PROGRAM], @@ -37,7 +44,7 @@ export class addSolanaTokens1704487070444 implements MigrationInterface { } } - public async down(queryRunner: QueryRunner): Promise { + async down(queryRunner: QueryRunner): Promise { const tokens = await queryRunner.query( `SELECT * FROM token WHERE "chainType" = $1 AND "address" != $2`, [ChainType.SOLANA, SOLANA_SYSTEM_PROGRAM], diff --git a/migration/data/seedTokens.ts b/migration/data/seedTokens.ts index aa1276f5e..d951485ae 100644 --- a/migration/data/seedTokens.ts +++ b/migration/data/seedTokens.ts @@ -1249,7 +1249,7 @@ const seedTokens: ITokenData[] = [ decimals: 18, networkId: NETWORK_IDS.MORDOR_ETC_TESTNET, }, - // TODO: Add solana token + // SOLANA mainnet { name: 'Solana native token', symbol: 'SOL', @@ -1331,6 +1331,39 @@ const seedTokens: ITokenData[] = [ chainType: ChainType.SOLANA, coingeckoId: COINGECKO_TOKEN_IDS.C98, }, + + + // SOLANA devnet + { + name: 'Solana native token', + symbol: 'SOL', + address: SOLANA_SYSTEM_PROGRAM, + decimals: 9, + networkId: NETWORK_IDS.SOLANA_DEVNET, + chainType: ChainType.SOLANA, + coingeckoId: COINGECKO_TOKEN_IDS.SOLANA, + }, + + { + // Mohammad has deployed it on solana devnet, so you can get some tokens from him + name: 'Test SPL token', + symbol: 'TEST-SPL-TOKEN', + address: 'BrEahxkTrCKfjVy36pLD2gvVoMCUMEb1PinrAFtvJqPX', + decimals: 9, + networkId: NETWORK_IDS.SOLANA_DEVNET, + chainType: ChainType.SOLANA, + }, + + // SOLANA testnet + { + name: 'Solana native token', + symbol: 'SOL', + address: SOLANA_SYSTEM_PROGRAM, + decimals: 9, + networkId: NETWORK_IDS.SOLANA_DEVNET, + chainType: ChainType.SOLANA, + coingeckoId: COINGECKO_TOKEN_IDS.SOLANA, + }, ]; export default seedTokens; From e64588eed0394b7014e885216fc62331508425e4 Mon Sep 17 00:00:00 2001 From: CarlosQ96 <92376054+CarlosQ96@users.noreply.github.com> Date: Mon, 15 Jan 2024 10:30:53 -0500 Subject: [PATCH 13/34] Import lost donations into DB cronjob (#1236) * import donations from env vars and add tests * Read LOST_DONATIONS_TX_HASHES from .env instead of config * Fix build error --------- Co-authored-by: Amin Latifi Co-authored-by: Mohammad Ranjbar Z --- config/example.env | 5 + config/test.env | 6 + ...91714385-addCoingeckoIdToOptimismTokens.ts | 47 +++ migration/data/seedTokens.ts | 13 + package.json | 1 + src/adapters/price/CoingeckoPriceAdapter.ts | 23 ++ src/adapters/price/PriceAdapterInterface.ts | 6 + src/server/bootstrap.ts | 5 + .../cronJobs/importLostDonationsJob.test.ts | 70 +++++ .../cronJobs/importLostDonationsJob.ts | 279 ++++++++++++++++++ src/services/donationService.ts | 2 +- test/pre-test-scripts.ts | 8 + 12 files changed, 464 insertions(+), 1 deletion(-) create mode 100644 migration/1704991714385-addCoingeckoIdToOptimismTokens.ts create mode 100644 src/services/cronJobs/importLostDonationsJob.test.ts create mode 100644 src/services/cronJobs/importLostDonationsJob.ts diff --git a/config/example.env b/config/example.env index 44a6ac344..c99136bb7 100644 --- a/config/example.env +++ b/config/example.env @@ -236,3 +236,8 @@ DISABLE_SERVER_CORS=false SOLANA_MAINNET_NODE_RPC_URL= SOLANA_DEVNET_NODE_RPC_URL= SOLANA_TEST_NODE_RPC_URL= +ENABLE_IMPORT_LOST_DONATIONS= +IMPORT_LOST_DONATIONS_CRONJOB_EXPRESSION= +LOST_DONATIONS_TX_HASHES= +LOST_DONATIONS_QF_ROUND= +LOST_DONATIONS_NETWORK_ID= diff --git a/config/test.env b/config/test.env index 86c6273e2..a052d42b7 100644 --- a/config/test.env +++ b/config/test.env @@ -199,3 +199,9 @@ DISABLE_SERVER_CORS=false SOLANA_MAINNET_NODE_RPC_URL= SOLANA_DEVNET_NODE_RPC_URL= SOLANA_TEST_NODE_RPC_URL= + +ENABLE_IMPORT_LOST_DONATIONS=false +IMPORT_LOST_DONATIONS_CRONJOB_EXPRESSION= +LOST_DONATIONS_TX_HASHES=0x4012421fbc2cecc85804f3b98bdd31bef04589dbac8292deca33e699868af01f,0x067e91368272dc73bc715a21a2af863a333cde20f410189fa53bceaa9cb8c86b +LOST_DONATIONS_QF_ROUND= +LOST_DONATIONS_NETWORK_ID=10 diff --git a/migration/1704991714385-addCoingeckoIdToOptimismTokens.ts b/migration/1704991714385-addCoingeckoIdToOptimismTokens.ts new file mode 100644 index 000000000..610a6dcee --- /dev/null +++ b/migration/1704991714385-addCoingeckoIdToOptimismTokens.ts @@ -0,0 +1,47 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; +import { NETWORK_IDS } from '../src/provider'; + +const tokenUpdates = { + ETH: 'ethereum', + OP: 'optimism', + WETH: 'weth', + DAI: 'dai', + LINK: 'chainlink', + WBTC: 'wrapped-bitcoin', + SNX: 'havven', + USDT: 'tether', + USDC: 'usd-coin', +}; + +export class addCoingeckoIdToOptimismTokens1704991714385 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + const tokens = await queryRunner.query( + `SELECT * FROM token WHERE "networkId" = $1 OR "networkId" = $2`, + [NETWORK_IDS.OPTIMISTIC, NETWORK_IDS.OPTIMISM_GOERLI], + ); + + for (const token of tokens) { + const coingeckoId = tokenUpdates[token.symbol]; + if (coingeckoId) { + await queryRunner.query( + ` + UPDATE token + SET "coingeckoId" = $1 + WHERE id = $2 + `, + [coingeckoId, token.id], + ); + } + } + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + UPDATE public.token + SET "coingeckoId" = NULL + WHERE "networkId" = 10 OR "networkId" = 420 + `); + } +} diff --git a/migration/data/seedTokens.ts b/migration/data/seedTokens.ts index aa1276f5e..0270f2cd3 100644 --- a/migration/data/seedTokens.ts +++ b/migration/data/seedTokens.ts @@ -1058,6 +1058,7 @@ const seedTokens: ITokenData[] = [ address: '0x0000000000000000000000000000000000000000', decimals: 18, networkId: NETWORK_IDS.OPTIMISM_GOERLI, + coingeckoId: 'ethereum', }, { name: 'OPTIMISM Goerli OP token', @@ -1065,6 +1066,7 @@ const seedTokens: ITokenData[] = [ address: '0x4200000000000000000000000000000000000042', decimals: 18, networkId: NETWORK_IDS.OPTIMISM_GOERLI, + coingeckoId: 'optimism', }, { name: 'Wrapped Ether', @@ -1072,6 +1074,7 @@ const seedTokens: ITokenData[] = [ address: '0x4200000000000000000000000000000000000006', decimals: 18, networkId: NETWORK_IDS.OPTIMISM_GOERLI, + coingeckoId: 'weth', }, { name: 'Dai', @@ -1079,6 +1082,7 @@ const seedTokens: ITokenData[] = [ address: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1', decimals: 18, networkId: NETWORK_IDS.OPTIMISM_GOERLI, + coingeckoId: 'dai', }, // OPTIMISTIC tokens @@ -1088,6 +1092,7 @@ const seedTokens: ITokenData[] = [ address: '0x0000000000000000000000000000000000000000', decimals: 18, networkId: NETWORK_IDS.OPTIMISTIC, + coingeckoId: 'ethereum', }, { name: 'OPTIMISTIC OP token', @@ -1095,6 +1100,7 @@ const seedTokens: ITokenData[] = [ address: '0x4200000000000000000000000000000000000042', decimals: 18, networkId: NETWORK_IDS.OPTIMISTIC, + coingeckoId: 'optimism', }, { name: 'Chainlink', @@ -1102,6 +1108,7 @@ const seedTokens: ITokenData[] = [ address: '0x350a791bfc2c21f9ed5d10980dad2e2638ffa7f6', decimals: 18, networkId: NETWORK_IDS.OPTIMISTIC, + coingeckoId: 'chainlink', }, { name: 'Wrapped Bitcoin', @@ -1109,6 +1116,7 @@ const seedTokens: ITokenData[] = [ address: '0x68f180fcce6836688e9084f035309e29bf0a2095', decimals: 8, networkId: NETWORK_IDS.OPTIMISTIC, + coingeckoId: 'wrapped-bitcoin', }, { name: 'Synthetix Network', @@ -1116,6 +1124,7 @@ const seedTokens: ITokenData[] = [ address: '0x8700daec35af8ff88c16bdf0418774cb3d7599b4', decimals: 18, networkId: NETWORK_IDS.OPTIMISTIC, + coingeckoId: 'havven', }, { name: 'Wrapped Ether', @@ -1123,6 +1132,7 @@ const seedTokens: ITokenData[] = [ address: '0x4200000000000000000000000000000000000006', decimals: 18, networkId: NETWORK_IDS.OPTIMISTIC, + coingeckoId: 'weth', }, { name: 'Tether', @@ -1130,6 +1140,7 @@ const seedTokens: ITokenData[] = [ address: '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58', decimals: 6, networkId: NETWORK_IDS.OPTIMISTIC, + coingeckoId: 'tether', }, { name: 'USD Coin', @@ -1137,6 +1148,7 @@ const seedTokens: ITokenData[] = [ address: '0x7F5c764cBc14f9669B88837ca1490cCa17c31607', decimals: 6, networkId: NETWORK_IDS.OPTIMISTIC, + coingeckoId: 'usd-coin', }, { name: 'Dai', @@ -1144,6 +1156,7 @@ const seedTokens: ITokenData[] = [ address: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1', decimals: 18, networkId: NETWORK_IDS.OPTIMISTIC, + coingeckoId: 'dai', }, // CELO tokens { diff --git a/package.json b/package.json index 7edc59e8f..999cacb43 100644 --- a/package.json +++ b/package.json @@ -166,6 +166,7 @@ "test:projectAddressRepository": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/repositories/projectAddressRepository.test.ts", "test:donationService": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/repositories/qfRoundHistoryRepository.test.ts ./src/services/donationService.test.ts", "test:userService": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/userService.test.ts", + "test:lostDonations": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/cronJobs/importLostDonationsJob.test.ts", "test:reactionsService": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/reactionsService.test.ts", "test:powerSnapshotService": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/powerSnapshotServices.test.ts", "test:transactionsService": "NODE_ENV=test mocha ./test/pre-test-scripts.ts src/services/chains/index.test.ts", diff --git a/src/adapters/price/CoingeckoPriceAdapter.ts b/src/adapters/price/CoingeckoPriceAdapter.ts index c7f2bae56..f22f2aaa0 100644 --- a/src/adapters/price/CoingeckoPriceAdapter.ts +++ b/src/adapters/price/CoingeckoPriceAdapter.ts @@ -1,4 +1,5 @@ import { + GetTokenPriceAtDateParams, GetTokenPriceParams, PriceAdapterInterface, } from './PriceAdapterInterface'; @@ -41,6 +42,28 @@ export class CoingeckoPriceAdapter implements PriceAdapterInterface { return result.priceUsd; } + async getTokenPriceAtDate( + params: GetTokenPriceAtDateParams, + ): Promise { + try { + const result = await axios.get( + // symbol in here means coingecko id for instance for ETC token the coingecko id is ethereum-classic + `https://api.coingecko.com/api/v3/coins/${params.symbol}/history?date=${params.date}`, + ); + + const priceUsd = result?.data?.market_data?.current_price?.usd; + if (!priceUsd) { + throw new Error( + `History Price not found for ${params.symbol} in coingecko`, + ); + } + return priceUsd; + } catch (e) { + logger.error('Error in CoingeckoHistoricPriceAdapter', e); + throw e; + } + } + async getTokenPrice(params: GetTokenPriceParams): Promise { try { const cachedPrice = await this.readTokenFromCache(params.symbol); diff --git a/src/adapters/price/PriceAdapterInterface.ts b/src/adapters/price/PriceAdapterInterface.ts index 9172d2f69..685cdb1dc 100644 --- a/src/adapters/price/PriceAdapterInterface.ts +++ b/src/adapters/price/PriceAdapterInterface.ts @@ -2,6 +2,12 @@ export interface GetTokenPriceParams { symbol: string; networkId: number; } + +export interface GetTokenPriceAtDateParams { + symbol: string; + date: string; +} + export interface PriceAdapterInterface { getTokenPrice(params: GetTokenPriceParams): Promise; } diff --git a/src/server/bootstrap.ts b/src/server/bootstrap.ts index 9c9b4e487..8e062debc 100644 --- a/src/server/bootstrap.ts +++ b/src/server/bootstrap.ts @@ -68,6 +68,7 @@ import { isTestEnv } from '../utils/utils'; import { runCheckActiveStatusOfQfRounds } from '../services/cronJobs/checkActiveStatusQfRounds'; import { runUpdateProjectCampaignsCacheJob } from '../services/cronJobs/updateProjectCampaignsCacheJob'; import { corsOptions } from './cors'; +import { runSyncLostDonations } from '../services/cronJobs/importLostDonationsJob'; Resource.validate = validate; @@ -335,6 +336,10 @@ export async function bootstrap() { runSyncPoignArtDonations(); } + if ((config.get('ENABLE_IMPORT_LOST_DONATIONS') as string) === 'true') { + runSyncLostDonations(); + } + if ( (config.get('FILL_POWER_SNAPSHOT_BALANCE_SERVICE_ACTIVE') as string) === 'true' diff --git a/src/services/cronJobs/importLostDonationsJob.test.ts b/src/services/cronJobs/importLostDonationsJob.test.ts new file mode 100644 index 000000000..01eb4f15b --- /dev/null +++ b/src/services/cronJobs/importLostDonationsJob.test.ts @@ -0,0 +1,70 @@ +import { assert } from 'chai'; +import { + createProjectData, + generateRandomEtheriumAddress, + saveProjectDirectlyToDb, + saveUserDirectlyToDb, +} from '../../../test/testUtils'; +import { importLostDonations } from './importLostDonationsJob'; +import { Donation } from '../../entities/donation'; + +describe('importLostDonations() test cases', importLostDonationsTestCases); + +function importLostDonationsTestCases() { + it('should create a eth simple transfer donation and erc20 token transfer donation', async () => { + // eth donation + const transactionIdEth = + '0x4012421fbc2cecc85804f3b98bdd31bef04589dbac8292deca33e699868af01f'; + const toWalletAddressEth = '0x6e8873085530406995170da467010565968c7c62'; + const walletAddressEth = '0x317bbc1927be411cd05615d2ffdf8d320c6c4052'; + const walletAddress2Eth = generateRandomEtheriumAddress(); + const userEth = await saveUserDirectlyToDb(walletAddressEth); + const user2Eth = await saveUserDirectlyToDb(walletAddress2Eth); + const project1 = await saveProjectDirectlyToDb({ + // test project with real tx + ...createProjectData(), + admin: String(user2Eth.id), + walletAddress: toWalletAddressEth, + }); + + // optimism donation + const transactionIdOP = + '0x067e91368272dc73bc715a21a2af863a333cde20f410189fa53bceaa9cb8c86b'; + const toWalletAddressOP = '0xa64f2228ccec96076c82abb903021c33859082f8'; + const walletAddressOP = '0x40891ce6e8574bb9118913a8a304195437f36213'; + const walletAddress2OP = generateRandomEtheriumAddress(); + const userOP = await saveUserDirectlyToDb(walletAddressOP); + const user2OP = await saveUserDirectlyToDb(walletAddress2OP); + const project2 = await saveProjectDirectlyToDb({ + // test project with real tx + ...createProjectData(), + admin: String(user2OP.id), + walletAddress: toWalletAddressOP, + }); + await importLostDonations(); + + const createdDonationEth = await Donation.createQueryBuilder('donation') + .where(`donation."transactionId" = :transactionIdEth`, { + transactionIdEth, + }) + .getOne(); + + const createdDonationOP = await Donation.createQueryBuilder('donation') + .where(`donation."transactionId" = :transactionIdOP`, { transactionIdOP }) + .getOne(); + + assert.equal(createdDonationEth?.toWalletAddress, toWalletAddressEth); + assert.equal(createdDonationEth?.fromWalletAddress, walletAddressEth); + assert.equal(createdDonationEth?.transactionId, transactionIdEth); + assert.equal(createdDonationEth?.projectId, project1.id); + assert.isTrue(createdDonationEth?.amount! > 0); + assert.isTrue(createdDonationEth?.valueUsd! > 0); + + assert.equal(createdDonationOP?.toWalletAddress, toWalletAddressOP); + assert.equal(createdDonationOP?.fromWalletAddress, walletAddressOP); + assert.equal(createdDonationOP?.transactionId, transactionIdOP); + assert.equal(createdDonationOP?.projectId, project2.id); + assert.isTrue(createdDonationOP?.amount! > 0); + assert.isTrue(createdDonationOP?.valueUsd! > 0); + }); +} diff --git a/src/services/cronJobs/importLostDonationsJob.ts b/src/services/cronJobs/importLostDonationsJob.ts new file mode 100644 index 000000000..f249a9e73 --- /dev/null +++ b/src/services/cronJobs/importLostDonationsJob.ts @@ -0,0 +1,279 @@ +import config from '../../config'; +import abiDecoder from 'abi-decoder'; +import { Donation } from '../../entities/donation'; +import { logger } from '../../utils/logger'; +import { schedule } from 'node-cron'; +import { normalizeAmount } from '../../utils/utils'; +import { NetworkTransactionInfo } from '../chains'; +import { getNetworkNativeToken, getProvider } from '../../provider'; +import { erc20ABI } from '../../assets/erc20ABI'; +import { gnosisSafeL2ABI } from '../../assets/gnosisSafeL2ABI'; +import { User } from '../../entities/user'; +import { Token } from '../../entities/token'; +import { Project } from '../../entities/project'; +import { calculateGivbackFactor } from '../givbackService'; +import moment from 'moment'; +import { + updateUserTotalDonated, + updateUserTotalReceived, +} from '../userService'; +import { toFixNumber, updateTotalDonationsOfProject } from '../donationService'; +import { + refreshProjectDonationSummaryView, + refreshProjectEstimatedMatchingView, +} from '../projectViewsService'; +import { CoingeckoPriceAdapter } from '../../adapters/price/CoingeckoPriceAdapter'; +import { QfRound } from '../../entities/qfRound'; + +// tslint:disable-next-line:no-var-requires +const ethers = require('ethers'); +abiDecoder.addABI(erc20ABI); + +const QF_ROUND_ID = config.get('LOST_DONATIONS_QF_ROUND'); +const NETWORK_ID = config.get('LOST_DONATIONS_NETWORK_ID'); + +const cronJobTime = + (config.get('IMPORT_LOST_DONATIONS_CRONJOB_EXPRESSION') as string) || + '0 0 * * 0'; + +// coma separated txHashes +const lostDonationsTxHashes = process.env.LOST_DONATIONS_TX_HASHES || ''; + +export const runSyncLostDonations = () => { + logger.debug('runSyncLostDonations() has been called'); + schedule(cronJobTime, async () => { + await importLostDonations(); + }); +}; + +const millisecondTimestampToDate = (timestamp: number): Date => { + return new Date(timestamp); +}; + +export const importLostDonations = async () => { + const networkId = NETWORK_ID ? Number(NETWORK_ID) : 10; // optimism + let donationParams; + const qfRoundId = QF_ROUND_ID ? Number(QF_ROUND_ID) : undefined; + try { + const qfRound = await QfRound.createQueryBuilder('qfRound') + .where(`qfRound.id = :id`, { id: qfRoundId }) + .getOne(); + + const donationTxHashes = lostDonationsTxHashes.split(','); + + for (const tx of donationTxHashes) { + const donationExists = await Donation.createQueryBuilder('donation') + .where(`lower(donation.transactionId) = :hash`, { + hash: tx.toLowerCase(), + }) + .getOne(); + + if (donationExists) continue; // avoid duplicates + + const transaction = await getProvider(networkId).getTransaction(tx); + if (!transaction) { + // Transaction not found + continue; + } + + const receipt = await getProvider(networkId).getTransactionReceipt(tx); + if (!receipt) { + // Transaction is not mined yet + // https://web3js.readthedocs.io/en/v1.2.0/web3-eth.html#gettransactionreceipt + continue; + } + + // decode transaction as ERC20 Token + const transactionData = abiDecoder.decodeMethod(transaction.data); + const erc20Token = transactionData?.params[0].value; + + // Search for the address in the correct location in the transaction + const userAddress = erc20Token + ? '0x' + receipt?.logs[1]?.topics[1]?.substring(26) + : transaction.from; + + const dbUser = await User.createQueryBuilder('user') + .where(`lower(user.walletAddress) = :address`, { + address: userAddress.toLowerCase(), + }) + .getOne(); + + if (!dbUser) continue; // User does not exist on giveth, not a UI donation, skip + + // Check if its an ERC-20 Token + let tokenInDB = await Token.createQueryBuilder('token') + .where(`lower(token.address) = :address`, { + address: erc20Token?.toLowerCase(), + }) + .andWhere(`token.networkId = :networkId`, { networkId }) + .getOne(); + + if (tokenInDB) { + // It's a token transfer donation + donationParams = await getDonationDetailForTokenTransfer( + tx, + networkId, + transaction, + receipt, + tokenInDB, + transactionData, + ); + + if (!donationParams) continue; // transaction not mined yet + } else if ( + !tokenInDB && + transaction.value && + transaction.value.gt(0) && + (!transaction.data || transaction.data === '0x') + ) { + // it's an eth transfer native token + const nativeTokenSymbol = getNetworkNativeToken(networkId); + const nativeToken = await Token.createQueryBuilder('token') + .where(`token.symbol = symbol`, { symbol: nativeTokenSymbol }) + .andWhere(`token."networkId" = :networkId`, { networkId }) + .getOne(); + + tokenInDB = nativeToken; + + donationParams = await getDonationDetailForNormalTransfer( + tx, + networkId, + transaction, + receipt, + nativeToken!, + ); + } else { + continue; // Not a transaction recognized by our logic + } + + const project = await Project.createQueryBuilder('project') + .leftJoinAndSelect('project.addresses', 'addresses') + .leftJoinAndSelect('project.adminUser', 'adminUser') + .where(`lower(addresses.address) = lower(:address)`, { + address: donationParams.to, + }) + .getOne(); + + if (!project) continue; // project doesn't exist on giveth, skip donation + + const donationDate = millisecondTimestampToDate( + donationParams.timestamp * 1000, + ); + const donationDateDbFormat = moment(donationDate).format( + 'YYYY-MM-DD HH:mm:ss', + ); + const donationDateCoingeckoFormat = + moment(donationDate).format('DD-MM-YYYY'); + + const coingeckoAdapter = new CoingeckoPriceAdapter(); + const ethereumPriceAtDate = await coingeckoAdapter.getTokenPriceAtDate({ + symbol: tokenInDB!.coingeckoId, + date: donationDateCoingeckoFormat, + }); + + const { givbackFactor, projectRank, powerRound, bottomRankInRound } = + await calculateGivbackFactor(project.id as number); + + const dbDonation = Donation.create({ + fromWalletAddress: donationParams.from.toLowerCase(), + toWalletAddress: donationParams.to.toLowerCase(), + transactionId: tx.toLowerCase(), + projectId: project.id, + currency: donationParams.currency, + tokenAddress: tokenInDB?.address, + amount: donationParams.amount, + valueUsd: toFixNumber(ethereumPriceAtDate * donationParams.amount, 4), + transactionNetworkId: networkId, + createdAt: donationDateDbFormat, + givbackFactor, + projectRank, + powerRound, + bottomRankInRound, + status: 'verified', + anonymous: false, + segmentNotified: true, + isTokenEligibleForGivback: tokenInDB?.isGivbackEligible, + isProjectVerified: project.verified, + qfRoundId: qfRound?.id, + }); + + await dbDonation.save(); + + await updateUserTotalDonated(dbUser.id); + await updateUserTotalReceived(project.adminUser?.id); + await updateTotalDonationsOfProject(project.id); + } + + // Figure out if its ideal to call this here or once, maybe in a button in adminjs + await refreshProjectEstimatedMatchingView(); + await refreshProjectDonationSummaryView(); + } catch (e) { + logger.error('importLostDonations() error', e); + } +}; + +// native token transfer ETH for optimism +async function getDonationDetailForNormalTransfer( + txHash: string, + networkId: number, + transaction: any, + receipt: any, + token: Token, +): Promise { + if (!receipt.status) { + return null; + } + + const block = await getProvider(networkId).getBlock( + transaction.blockNumber as number, + ); + + const transactionTo = transaction.to; + const amount = ethers.utils.formatEther(transaction.value); + + return { + from: transaction.from, + timestamp: block.timestamp as number, + to: transactionTo as string, + hash: txHash, + amount, + currency: token.symbol, + }; +} + +async function getDonationDetailForTokenTransfer( + txHash: string, + networkId: number, + transaction: any, + receipt: any, + token: Token, + transactionData?: any, +): Promise { + if (!receipt.status || !transactionData) { + return null; + } + + const transactionFrom: string = + '0x' + receipt?.logs[1]?.topics[1]?.substring(26); + const transactionTo: string = + '0x' + receipt?.logs[1]?.topics[2]?.substring(26); + + const amount = Number( + ethers.utils.formatUnits( + ethers.BigNumber.from(receipt?.logs[1].data), + 'ether', + ), + ); + + const block = await getProvider(networkId).getBlock( + transaction.blockNumber as number, + ); + return { + from: transactionFrom, + timestamp: block.timestamp as number, + hash: txHash, + to: transactionTo!, + amount, + currency: token.symbol, + }; +} diff --git a/src/services/donationService.ts b/src/services/donationService.ts index 388f7c601..f61b39dc0 100644 --- a/src/services/donationService.ts +++ b/src/services/donationService.ts @@ -256,7 +256,7 @@ export const isTokenAcceptableForProject = async (inputData: { } }; -const toFixNumber = (input: number, digits: number): number => { +export const toFixNumber = (input: number, digits: number): number => { return convertExponentialNumber(Number(input.toFixed(digits))); }; diff --git a/test/pre-test-scripts.ts b/test/pre-test-scripts.ts index c1fad384e..f4a1a3aaf 100644 --- a/test/pre-test-scripts.ts +++ b/test/pre-test-scripts.ts @@ -162,6 +162,10 @@ async function seedTokens() { }; if (token.symbol === 'OP') { (tokenData as any).order = 2; + (tokenData as any).coingeckoId = 'optimism'; + } + if (token.symbol === 'ETH') { + (tokenData as any).coingeckoId = 'ethereum'; } await Token.create(tokenData as Token).save(); } @@ -173,6 +177,10 @@ async function seedTokens() { }; if (token.symbol === 'OP') { (tokenData as any).order = 2; + (tokenData as any).coingeckoId = 'optimism'; + } + if (token.symbol === 'ETH') { + (tokenData as any).coingeckoId = 'ethereum'; } await Token.create(tokenData as Token).save(); } From 6ae3a132a67351793d7520cdbe166571fd92fd65 Mon Sep 17 00:00:00 2001 From: Carlos Date: Mon, 15 Jan 2024 23:28:19 -0500 Subject: [PATCH 14/34] setup notification center disabling and reduce notifications --- config/example.env | 2 + config/test.env | 2 + .../NotificationCenterAdapter.ts | 43 +++++++------------ src/resolvers/projectResolver.ts | 14 +----- src/resolvers/reactionResolver.ts | 4 -- src/services/donationService.ts | 10 +---- 6 files changed, 21 insertions(+), 54 deletions(-) diff --git a/config/example.env b/config/example.env index c99136bb7..c5dbbe3c6 100644 --- a/config/example.env +++ b/config/example.env @@ -241,3 +241,5 @@ IMPORT_LOST_DONATIONS_CRONJOB_EXPRESSION= LOST_DONATIONS_TX_HASHES= LOST_DONATIONS_QF_ROUND= LOST_DONATIONS_NETWORK_ID= + +DISABLE_NOTIFICATION_CENTER= \ No newline at end of file diff --git a/config/test.env b/config/test.env index a052d42b7..efb8008cf 100644 --- a/config/test.env +++ b/config/test.env @@ -205,3 +205,5 @@ IMPORT_LOST_DONATIONS_CRONJOB_EXPRESSION= LOST_DONATIONS_TX_HASHES=0x4012421fbc2cecc85804f3b98bdd31bef04589dbac8292deca33e699868af01f,0x067e91368272dc73bc715a21a2af863a333cde20f410189fa53bceaa9cb8c86b LOST_DONATIONS_QF_ROUND= LOST_DONATIONS_NETWORK_ID=10 + +DISABLE_NOTIFICATION_CENTER=false \ No newline at end of file diff --git a/src/adapters/notifications/NotificationCenterAdapter.ts b/src/adapters/notifications/NotificationCenterAdapter.ts index ebff3cdf7..939bdc5c9 100644 --- a/src/adapters/notifications/NotificationCenterAdapter.ts +++ b/src/adapters/notifications/NotificationCenterAdapter.ts @@ -24,6 +24,7 @@ import { buildProjectLink } from './NotificationCenterUtils'; const notificationCenterUsername = process.env.NOTIFICATION_CENTER_USERNAME; const notificationCenterPassword = process.env.NOTIFICATION_CENTER_PASSWORD; const notificationCenterBaseUrl = process.env.NOTIFICATION_CENTER_BASE_URL; +const disableNotificationCenter = process.env.DISABLE_NOTIFICATION_CENTER; const numberOfSendNotificationsConcurrentJob = Number( @@ -697,7 +698,6 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface { async projectReactivated(params: { project: Project }): Promise { const { project } = params; - const projectOwner = project?.adminUser as User; const now = Date.now(); const supporters = await findUsersWhoSupportProject(project.id); await sendProjectRelatedNotificationsQueue.addBulk( @@ -713,23 +713,6 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface { }, })), ); - - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_ACTIVATED, - - sendEmail: true, - segment: { - analyticsUserId: projectOwner.segmentUserId(), - anonymousId: projectOwner.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `project-reactivated-${ - project.id - }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - }); } async projectSavedAsDraft(params: { project: Project }): Promise { @@ -954,11 +937,13 @@ const callSendNotification = async ( data: SendNotificationBody, ): Promise => { try { - await axios.post(`${notificationCenterBaseUrl}/notifications`, data, { - headers: { - Authorization: authorizationHeader(), - }, - }); + if (disableNotificationCenter !== 'true') { + await axios.post(`${notificationCenterBaseUrl}/notifications`, data, { + headers: { + Authorization: authorizationHeader(), + }, + }); + } } catch (e) { logger.error('callSendNotification error', { errorResponse: e?.response?.data, @@ -973,11 +958,13 @@ const callBatchNotification = async ( data: SendBatchNotificationBody, ): Promise => { try { - await axios.post(`${notificationCenterBaseUrl}/notificationsBulk`, data, { - headers: { - Authorization: authorizationHeader(), - }, - }); + if (disableNotificationCenter !== 'true') { + await axios.post(`${notificationCenterBaseUrl}/notificationsBulk`, data, { + headers: { + Authorization: authorizationHeader(), + }, + }); + } } catch (e) { logger.error('callBatchNotification error', { errorResponse: e?.response?.data, diff --git a/src/resolvers/projectResolver.ts b/src/resolvers/projectResolver.ts index b21ed5f69..02f3f4e08 100644 --- a/src/resolvers/projectResolver.ts +++ b/src/resolvers/projectResolver.ts @@ -1054,10 +1054,6 @@ export class ProjectResolver { project.addresses = await findProjectRecipientAddressByProjectId({ projectId, }); - - // Edit emails - await getNotificationAdapter().projectEdited({ project }); - return project; } @@ -1331,10 +1327,6 @@ export class ProjectResolver { await getNotificationAdapter().projectSavedAsDraft({ project, }); - } else { - await getNotificationAdapter().projectPublished({ - project, - }); } return project; @@ -1977,11 +1969,7 @@ export class ProjectResolver { await project.save(); - if (project.prevStatusId === ProjStatus.drafted) { - await getNotificationAdapter().projectPublished({ - project, - }); - } else { + if (project.prevStatusId !== ProjStatus.drafted) { await getNotificationAdapter().projectReactivated({ project, }); diff --git a/src/resolvers/reactionResolver.ts b/src/resolvers/reactionResolver.ts index 11b4352ed..5cc0df298 100644 --- a/src/resolvers/reactionResolver.ts +++ b/src/resolvers/reactionResolver.ts @@ -213,10 +213,6 @@ export class ReactionResolver { // commit transaction now: await queryRunner.commitTransaction(); - await getNotificationAdapter().projectReceivedHeartReaction({ - project, - userId: user.userId, - }); return reaction; } catch (e) { logger.error('like project error', e); diff --git a/src/services/donationService.ts b/src/services/donationService.ts index f61b39dc0..922f40345 100644 --- a/src/services/donationService.ts +++ b/src/services/donationService.ts @@ -462,20 +462,12 @@ export const sendSegmentEventForDonation = async (params: { } const donorUser = await findUserById(donation.userId); const projectOwner = project.adminUser; - if (projectOwner) { + if (projectOwner && donation.valueUsd > 1) { await getNotificationAdapter().donationReceived({ donation, project, }); } - - if (donorUser) { - await getNotificationAdapter().donationSent({ - donation, - project, - donor: donorUser, - }); - } }; export const insertDonationsFromQfRoundHistory = async (): Promise => { From 5d4bef5de2040e8b97e0ae7fd3857974b875ec59 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Tue, 16 Jan 2024 11:58:49 +0330 Subject: [PATCH 15/34] Change filling networkId for solana --- migration/data/seedTokens.ts | 1 - src/repositories/projectRepository.ts | 6 +++++- .../projectVerificationRepository.ts | 8 ++++++-- src/resolvers/donationResolver.ts | 17 ++++++++--------- src/resolvers/projectResolver.ts | 11 +++++++++-- 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/migration/data/seedTokens.ts b/migration/data/seedTokens.ts index d951485ae..7245f4bd4 100644 --- a/migration/data/seedTokens.ts +++ b/migration/data/seedTokens.ts @@ -1332,7 +1332,6 @@ const seedTokens: ITokenData[] = [ coingeckoId: COINGECKO_TOKEN_IDS.C98, }, - // SOLANA devnet { name: 'Solana native token', diff --git a/src/repositories/projectRepository.ts b/src/repositories/projectRepository.ts index da963f7af..6ae890999 100644 --- a/src/repositories/projectRepository.ts +++ b/src/repositories/projectRepository.ts @@ -300,7 +300,11 @@ export const updateProjectWithVerificationForm = async ( address: relatedAddress.address, // Frontend doesn't send networkId for solana addresses so we set it to default solana chain id - networkId: relatedAddress.networkId || getDefaultSolanaChainId(), + networkId: + relatedAddress.chainType === ChainType.SOLANA + ? getDefaultSolanaChainId() + : relatedAddress.networkId, + projectId: verificationForm.projectId, userId: verificationForm.user?.id, project, diff --git a/src/repositories/projectVerificationRepository.ts b/src/repositories/projectVerificationRepository.ts index 14aa3b2bf..f55ef4c35 100644 --- a/src/repositories/projectVerificationRepository.ts +++ b/src/repositories/projectVerificationRepository.ts @@ -18,7 +18,7 @@ import { } from '../utils/errorMessages'; import { User } from '../entities/user'; import { getDefaultSolanaChainId } from '../services/chains'; -import { add } from 'lodash'; +import { ChainType } from '../types/network'; export const createProjectVerificationForm = async (params: { userId: number; @@ -310,7 +310,11 @@ export const updateManagingFundsOfProjectVerification = async (params: { managingFunds.relatedAddresses = managingFunds.relatedAddresses.map( address => { // because frontend sends 0 for solana addresses - address.networkId = address.networkId || getDefaultSolanaChainId(); + address.networkId = + address.chainType === ChainType.SOLANA + ? getDefaultSolanaChainId() + : address.networkId; + return address; }, ); diff --git a/src/resolvers/donationResolver.ts b/src/resolvers/donationResolver.ts index 44cebd303..b5c7c0b44 100644 --- a/src/resolvers/donationResolver.ts +++ b/src/resolvers/donationResolver.ts @@ -4,7 +4,6 @@ import { ArgsType, Ctx, Field, - Float, InputType, Int, Mutation, @@ -15,7 +14,6 @@ import { } from 'type-graphql'; import { Service } from 'typedi'; import { Max, Min } from 'class-validator'; -import { getOurTokenList } from '@giveth/monoswap'; import { Donation, DONATION_STATUS, SortField } from '../entities/donation'; import { ApolloContext } from '../types/ApolloContext'; import { Project, ProjStatus } from '../entities/project'; @@ -26,7 +24,6 @@ import SentryLogger from '../sentryLogger'; import { i18n, translationErrorMessagesKeys } from '../utils/errorMessages'; import { NETWORK_IDS } from '../provider'; import { - getMonoSwapTokenPrices, isTokenAcceptableForProject, syncDonationStatusWithBlockchainNetwork, updateDonationPricesAndValues, @@ -41,7 +38,6 @@ import { import { logger } from '../utils/logger'; import { findUserById, - isFirstTimeDonor, setUserAsReferrer, } from '../repositories/userRepository'; import { @@ -60,12 +56,11 @@ import { findProjectRecipientAddressByNetworkId } from '../repositories/projectA import { MainCategory } from '../entities/mainCategory'; import { findProjectById } from '../repositories/projectRepository'; import { AppDataSource } from '../orm'; -import { CHAIN_ID } from '@giveth/monoswap/dist/src/sdk/sdkFactory'; -import { ethers } from 'ethers'; import { getChainvineReferralInfoForDonation } from '../services/chainvineReferralService'; import { relatedActiveQfRoundForProject } from '../services/qfRoundService'; import { detectAddressChainType } from '../utils/networks'; import { ChainType } from '../types/network'; +import { getDefaultSolanaChainId } from '../services/chains'; @ObjectType() class PaginateDonations { @@ -631,13 +626,17 @@ export class DonationResolver { throw new Error(i18n.__(translationErrorMessagesKeys.UN_AUTHORIZED)); } const chainType = detectAddressChainType(donorUser.walletAddress!); + const networkId = + chainType === ChainType.SOLANA + ? getDefaultSolanaChainId() + : transactionNetworkId; try { validateWithJoiSchema( { amount, transactionId, - transactionNetworkId, + transactionNetworkId: networkId, anonymous, tokenAddress, token, @@ -701,7 +700,7 @@ export class DonationResolver { const projectRelatedAddress = await findProjectRecipientAddressByNetworkId({ projectId, - networkId: transactionNetworkId, + networkId, }); if (!projectRelatedAddress) { throw new Error( @@ -725,7 +724,7 @@ export class DonationResolver { amount: Number(amount), transactionId: transactionTx, isFiat: Boolean(transakId), - transactionNetworkId: Number(transactionNetworkId), + transactionNetworkId: networkId, currency: token, user: donorUser, tokenAddress, diff --git a/src/resolvers/projectResolver.ts b/src/resolvers/projectResolver.ts index b6bdbf285..7906faa7a 100644 --- a/src/resolvers/projectResolver.ts +++ b/src/resolvers/projectResolver.ts @@ -1044,7 +1044,11 @@ export class ProjectResolver { chainType: relatedAddress.chainType, // Frontend doesn't send networkId for solana addresses so we set it to default solana chain id - networkId: relatedAddress.networkId || getDefaultSolanaChainId(), + networkId: + relatedAddress.chainType === ChainType.SOLANA + ? getDefaultSolanaChainId() + : relatedAddress.networkId, + isRecipient: true, }; }), @@ -1309,7 +1313,10 @@ export class ProjectResolver { address: chainType === ChainType.EVM ? address.toLowerCase() : address, chainType, - networkId, + networkId: + chainType === ChainType.SOLANA + ? getDefaultSolanaChainId() + : networkId, isRecipient: true, }; }), From bc1d6db6689a9120e549d531e360b51611cd9cfa Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Tue, 16 Jan 2024 12:23:26 +0330 Subject: [PATCH 16/34] Fix migration files for adding spl tokens on solana --- migration/1703044586989-addSolanaToken.ts | 6 +++--- ...dSolanaTokens.ts => 1704487070444-addSolanaSplTokens.ts} | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename migration/{1704487070444-addSolanaTokens.ts => 1704487070444-addSolanaSplTokens.ts} (96%) diff --git a/migration/1703044586989-addSolanaToken.ts b/migration/1703044586989-addSolanaToken.ts index c21ca9d4a..1bb1c526a 100644 --- a/migration/1703044586989-addSolanaToken.ts +++ b/migration/1703044586989-addSolanaToken.ts @@ -1,10 +1,10 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; -import { NETWORK_IDS } from '../src/provider'; -import { Token } from '../src/entities/token'; import seedTokens from './data/seedTokens'; import { ChainType } from '../src/types/network'; import { SOLANA_SYSTEM_PROGRAM } from '../src/utils/networks'; import { ENVIRONMENTS } from '../src/utils/utils'; +import { MigrationInterface, QueryRunner } from 'typeorm'; +import { NETWORK_IDS } from '../src/provider'; +import { Token } from '../src/entities/token'; export class addSolanaToken1703044586989 implements MigrationInterface { async up(queryRunner: QueryRunner): Promise { diff --git a/migration/1704487070444-addSolanaTokens.ts b/migration/1704487070444-addSolanaSplTokens.ts similarity index 96% rename from migration/1704487070444-addSolanaTokens.ts rename to migration/1704487070444-addSolanaSplTokens.ts index 2e257c435..1168ce273 100644 --- a/migration/1704487070444-addSolanaTokens.ts +++ b/migration/1704487070444-addSolanaSplTokens.ts @@ -6,7 +6,7 @@ import { ChainType } from '../src/types/network'; import { SOLANA_SYSTEM_PROGRAM } from '../src/utils/networks'; import { ENVIRONMENTS } from '../src/utils/utils'; -export class addSolanaTokens1704487070444 implements MigrationInterface { +export class addSolanaSplTokens1704487070444 implements MigrationInterface { async up(queryRunner: QueryRunner): Promise { let tokensData; if (process.env.ENVIRONMENT === ENVIRONMENTS.PRODUCTION) { From 8b08f091982e92e219ec44a9124520711e9104dd Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Tue, 16 Jan 2024 12:53:58 +0330 Subject: [PATCH 17/34] Modify create donation test cases for check filling networkId of solana donations --- config/example.env | 1 + config/test.env | 2 ++ src/resolvers/donationResolver.test.ts | 9 ++++++--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/config/example.env b/config/example.env index 44a6ac344..279f9828f 100644 --- a/config/example.env +++ b/config/example.env @@ -236,3 +236,4 @@ DISABLE_SERVER_CORS=false SOLANA_MAINNET_NODE_RPC_URL= SOLANA_DEVNET_NODE_RPC_URL= SOLANA_TEST_NODE_RPC_URL= +SOLANA_CHAIN_ID=103 diff --git a/config/test.env b/config/test.env index 86c6273e2..f03ee7cbe 100644 --- a/config/test.env +++ b/config/test.env @@ -199,3 +199,5 @@ DISABLE_SERVER_CORS=false SOLANA_MAINNET_NODE_RPC_URL= SOLANA_DEVNET_NODE_RPC_URL= SOLANA_TEST_NODE_RPC_URL= + +SOLANA_CHAIN_ID=103 diff --git a/src/resolvers/donationResolver.test.ts b/src/resolvers/donationResolver.test.ts index bd8e22202..aa00908de 100644 --- a/src/resolvers/donationResolver.test.ts +++ b/src/resolvers/donationResolver.test.ts @@ -57,6 +57,7 @@ import { findProjectById } from '../repositories/projectRepository'; import { addOrUpdatePowerSnapshotBalances } from '../repositories/powerBalanceSnapshotRepository'; import { findPowerSnapshots } from '../repositories/powerSnapshotRepository'; import { ChainType } from '../types/network'; +import { getDefaultSolanaChainId } from '../services/chains'; // tslint:disable-next-line:no-var-requires const moment = require('moment'); @@ -820,7 +821,7 @@ function createDonationTestCases() { await qfRound.save(); }); - it('should create a solana donation succesfully', async () => { + it('should create a solana donation successfully', async () => { const project = await saveProjectDirectlyToDb(createProjectData()); await project.save(); @@ -839,7 +840,7 @@ function createDonationTestCases() { query: createDonationMutation, variables: { projectId: project.id, - transactionNetworkId: NETWORK_IDS.SOLANA_MAINNET, + transactionNetworkId: 0, chainType: ChainType.SOLANA, transactionId, nonce: 1, @@ -861,10 +862,11 @@ function createDonationTestCases() { }, }); assert.equal(donation?.transactionId, transactionId); + assert.equal(donation?.transactionNetworkId, getDefaultSolanaChainId()); assert.equal(donation?.chainType, ChainType.SOLANA); }); - it('should create a solana donation succesfully - 2', async () => { + it('should create a solana donation successfully - 2', async () => { const project = await saveProjectDirectlyToDb(createProjectData()); await project.save(); @@ -906,6 +908,7 @@ function createDonationTestCases() { }, }); assert.equal(donation?.transactionId, transactionId); + assert.equal(donation?.transactionNetworkId, getDefaultSolanaChainId()); assert.equal(donation?.chainType, ChainType.SOLANA); }); From 4a6dd8c4082fceee0b822c1f14a9c0a0b6968d41 Mon Sep 17 00:00:00 2001 From: Carlos Date: Tue, 16 Jan 2024 10:26:21 -0500 Subject: [PATCH 18/34] comment out notification center methods interface --- .../NotificationCenterAdapter.ts | 1348 +++++++++-------- 1 file changed, 687 insertions(+), 661 deletions(-) diff --git a/src/adapters/notifications/NotificationCenterAdapter.ts b/src/adapters/notifications/NotificationCenterAdapter.ts index 939bdc5c9..ef6c510aa 100644 --- a/src/adapters/notifications/NotificationCenterAdapter.ts +++ b/src/adapters/notifications/NotificationCenterAdapter.ts @@ -86,27 +86,28 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface { donation: Donation; project: Project; }): Promise { - const { project, donation } = params; - const user = project.adminUser as User; - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.DONATION_RECEIVED, - sendEmail: true, - segment: { - analyticsUserId: user.segmentUserId(), - anonymousId: user.segmentUserId(), - payload: getSegmentDonationAttributes({ - donation, - project, - user, - }), - }, - trackId: - 'donation-received-' + - donation.transactionNetworkId + - '-' + - donation.transactionId, - }); + return; + // const { project, donation } = params; + // const user = project.adminUser as User; + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.DONATION_RECEIVED, + // sendEmail: true, + // segment: { + // analyticsUserId: user.segmentUserId(), + // anonymousId: user.segmentUserId(), + // payload: getSegmentDonationAttributes({ + // donation, + // project, + // user, + // }), + // }, + // trackId: + // 'donation-received-' + + // donation.transactionNetworkId + + // '-' + + // donation.transactionId, + // }); } async donationSent(params: { @@ -114,311 +115,323 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface { project: Project; donor: User; }): Promise { - const { project, donor, donation } = params; - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.MADE_DONATION, - user: { - email: donor.email, - walletAddress: donor.walletAddress as string, - }, - sendEmail: true, - segment: { - analyticsUserId: donor.segmentUserId(), - anonymousId: donor.segmentUserId(), - payload: { - ...getSegmentDonationAttributes({ - donation, - project, - user: donor, - }), - - // We just want this to be donation sent event not made donation, so don put it in getSegmentDonationAttributes() - // see https://github.com/Giveth/impact-graph/pull/716 - fromWalletAddress: donation.fromWalletAddress.toLowerCase(), - }, - }, - trackId: - 'donation-sent-' + - donation.transactionNetworkId + - '-' + - donation.transactionId, - }); + return; + // const { project, donor, donation } = params; + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.MADE_DONATION, + // user: { + // email: donor.email, + // walletAddress: donor.walletAddress as string, + // }, + // sendEmail: true, + // segment: { + // analyticsUserId: donor.segmentUserId(), + // anonymousId: donor.segmentUserId(), + // payload: { + // ...getSegmentDonationAttributes({ + // donation, + // project, + // user: donor, + // }), + + // // We just want this to be donation sent event not made donation, so don put it in getSegmentDonationAttributes() + // // see https://github.com/Giveth/impact-graph/pull/716 + // fromWalletAddress: donation.fromWalletAddress.toLowerCase(), + // }, + // }, + // trackId: + // 'donation-sent-' + + // donation.transactionNetworkId + + // '-' + + // donation.transactionId, + // }); } async projectVerified(params: { project: Project }): Promise { - const { project } = params; - const projectOwner = project.adminUser as User; - const now = new Date(); - - const supporters = await findUsersWhoSupportProject(project.id); - await sendProjectRelatedNotificationsQueue.addBulk( - supporters.map(user => ({ - data: { - project, - eventName: - NOTIFICATIONS_EVENT_NAMES.PROJECT_VERIFIED_USERS_WHO_SUPPORT, - user, - trackId: `project-verified-${ - project.id - }-${user.walletAddress.toLowerCase()}-${now}`, - }, - })), - ); - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_VERIFIED, - sendEmail: true, - segment: { - analyticsUserId: projectOwner.segmentUserId(), - anonymousId: projectOwner.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `project-verified-${ - project.id - }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - }); + return; + // const { project } = params; + // const projectOwner = project.adminUser as User; + // const now = new Date(); + + // const supporters = await findUsersWhoSupportProject(project.id); + // await sendProjectRelatedNotificationsQueue.addBulk( + // supporters.map(user => ({ + // data: { + // project, + // eventName: + // NOTIFICATIONS_EVENT_NAMES.PROJECT_VERIFIED_USERS_WHO_SUPPORT, + // user, + // trackId: `project-verified-${ + // project.id + // }-${user.walletAddress.toLowerCase()}-${now}`, + // }, + // })), + // ); + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_VERIFIED, + // sendEmail: true, + // segment: { + // analyticsUserId: projectOwner.segmentUserId(), + // anonymousId: projectOwner.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `project-verified-${ + // project.id + // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + // }); } async projectBoosted(params: { projectId: number; userId: number; }): Promise { - const { projectId, userId } = params; - const project = (await findProjectById(projectId)) as Project; - sendProjectRelatedNotificationsQueue.add({ - project: project as Project, - eventName: - project.adminUser?.id === userId - ? // We send different notifications when project owner or someone else boost the project https://github.com/Giveth/notification-center/issues/41 - NOTIFICATIONS_EVENT_NAMES.PROJECT_BOOSTED_BY_PROJECT_OWNER - : NOTIFICATIONS_EVENT_NAMES.PROJECT_BOOSTED, - - // With adding trackId to notification, notification-center would not create new notification - // If there is already a notification with this trackId in DB - trackId: generateTrackId({ - userId, - projectId: project?.id as number, - action: 'boostProject', - }), - }); + return; + // const { projectId, userId } = params; + // const project = (await findProjectById(projectId)) as Project; + // sendProjectRelatedNotificationsQueue.add({ + // project: project as Project, + // eventName: + // project.adminUser?.id === userId + // ? // We send different notifications when project owner or someone else boost the project https://github.com/Giveth/notification-center/issues/41 + // NOTIFICATIONS_EVENT_NAMES.PROJECT_BOOSTED_BY_PROJECT_OWNER + // : NOTIFICATIONS_EVENT_NAMES.PROJECT_BOOSTED, + + // // With adding trackId to notification, notification-center would not create new notification + // // If there is already a notification with this trackId in DB + // trackId: generateTrackId({ + // userId, + // projectId: project?.id as number, + // action: 'boostProject', + // }), + // }); } async projectBoostedBatch(params: { projectIds: number[]; userId: number; }): Promise { - const { userId, projectIds } = params; - for (const projectId of projectIds) { - await this.projectBoosted({ - userId, - projectId, - }); - } + return; + // const { userId, projectIds } = params; + // for (const projectId of projectIds) { + // await this.projectBoosted({ + // userId, + // projectId, + // }); + // } } async projectBadgeRevoked(params: { project: Project }): Promise { - const { project } = params; - const user = project.adminUser as User; - const supporters = await findUsersWhoSupportProject(project.id); - const now = new Date(); - await sendProjectRelatedNotificationsQueue.addBulk( - supporters.map(u => ({ - data: { - project, - eventName: - NOTIFICATIONS_EVENT_NAMES.PROJECT_UNVERIFIED_USERS_WHO_SUPPORT, - user: u, - trackId: `project-unverified-${ - project.id - }-${u.walletAddress.toLowerCase()}-${now}}`, - }, - })), - ); - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKED, - sendEmail: true, - segment: { - analyticsUserId: user.segmentUserId(), - anonymousId: user.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `project-badge-revoked-${ - project.id - }-${user.walletAddress?.toLowerCase()}-${now}`, - }); + return; + // const { project } = params; + // const user = project.adminUser as User; + // const supporters = await findUsersWhoSupportProject(project.id); + // const now = new Date(); + // await sendProjectRelatedNotificationsQueue.addBulk( + // supporters.map(u => ({ + // data: { + // project, + // eventName: + // NOTIFICATIONS_EVENT_NAMES.PROJECT_UNVERIFIED_USERS_WHO_SUPPORT, + // user: u, + // trackId: `project-unverified-${ + // project.id + // }-${u.walletAddress.toLowerCase()}-${now}}`, + // }, + // })), + // ); + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKED, + // sendEmail: true, + // segment: { + // analyticsUserId: user.segmentUserId(), + // anonymousId: user.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `project-badge-revoked-${ + // project.id + // }-${user.walletAddress?.toLowerCase()}-${now}`, + // }); } async projectBadgeRevokeReminder(params: { project: Project; }): Promise { - const { project } = params; - const user = project.adminUser as User; - const now = new Date(); - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKE_REMINDER, - sendEmail: true, - segment: { - analyticsUserId: user.segmentUserId(), - anonymousId: user.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `project-badge-revoke-reminder-${ - project.id - }-${user.walletAddress?.toLowerCase()}-${now}`, - }); + return; + // const { project } = params; + // const user = project.adminUser as User; + // const now = new Date(); + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKE_REMINDER, + // sendEmail: true, + // segment: { + // analyticsUserId: user.segmentUserId(), + // anonymousId: user.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `project-badge-revoke-reminder-${ + // project.id + // }-${user.walletAddress?.toLowerCase()}-${now}`, + // }); } async projectBadgeRevokeWarning(params: { project: Project }): Promise { - const { project } = params; - const user = project.adminUser as User; - const now = new Date(); - - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKE_WARNING, - sendEmail: true, - segment: { - analyticsUserId: user.segmentUserId(), - anonymousId: user.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `project-badge-revoke-warning-${ - project.id - }-${user.walletAddress?.toLowerCase()}-${now}`, - }); + return; + // const { project } = params; + // const user = project.adminUser as User; + // const now = new Date(); + + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKE_WARNING, + // sendEmail: true, + // segment: { + // analyticsUserId: user.segmentUserId(), + // anonymousId: user.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `project-badge-revoke-warning-${ + // project.id + // }-${user.walletAddress?.toLowerCase()}-${now}`, + // }); } async projectBadgeRevokeLastWarning(params: { project: Project; }): Promise { - const { project } = params; - const user = project.adminUser as User; - const now = Date.now(); - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKE_LAST_WARNING, - sendEmail: true, - segment: { - analyticsUserId: user.segmentUserId(), - anonymousId: user.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `project-badge-revoke-last-warning-${ - project.id - }-${user.walletAddress?.toLowerCase()}-${now}`, - }); + return; + // const { project } = params; + // const user = project.adminUser as User; + // const now = Date.now(); + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKE_LAST_WARNING, + // sendEmail: true, + // segment: { + // analyticsUserId: user.segmentUserId(), + // anonymousId: user.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `project-badge-revoke-last-warning-${ + // project.id + // }-${user.walletAddress?.toLowerCase()}-${now}`, + // }); } async projectBadgeUpForRevoking(params: { project: Project }): Promise { - const { project } = params; - const user = project.adminUser as User; - const now = Date.now(); - - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_UP_FOR_REVOKING, - sendEmail: true, - segment: { - analyticsUserId: user.segmentUserId(), - anonymousId: user.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `project-badge-up-for-revoking-${ - project.id - }-${user.walletAddress?.toLowerCase()}-${now}`, - }); + return; + // const { project } = params; + // const user = project.adminUser as User; + // const now = Date.now(); + + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_UP_FOR_REVOKING, + // sendEmail: true, + // segment: { + // analyticsUserId: user.segmentUserId(), + // anonymousId: user.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `project-badge-up-for-revoking-${ + // project.id + // }-${user.walletAddress?.toLowerCase()}-${now}`, + // }); } async projectUnVerified(params: { project: Project }): Promise { - const { project } = params; - const user = project.adminUser as User; - const now = Date.now(); - - const supporters = await findUsersWhoSupportProject(project.id); - await sendProjectRelatedNotificationsQueue.addBulk( - supporters.map(u => ({ - data: { - project, - eventName: - NOTIFICATIONS_EVENT_NAMES.PROJECT_UNVERIFIED_USERS_WHO_SUPPORT, - user: u, - trackId: `project-unverified-${ - project.id - }-${u.walletAddress?.toLowerCase()}-${now}`, - }, - })), - ); - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_UNVERIFIED, - sendEmail: true, - segment: { - analyticsUserId: user.segmentUserId(), - anonymousId: user.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `project-unverified-${ - project.id - }-${user.walletAddress?.toLowerCase()}-${now}`, - }); + return; + // const { project } = params; + // const user = project.adminUser as User; + // const now = Date.now(); + + // const supporters = await findUsersWhoSupportProject(project.id); + // await sendProjectRelatedNotificationsQueue.addBulk( + // supporters.map(u => ({ + // data: { + // project, + // eventName: + // NOTIFICATIONS_EVENT_NAMES.PROJECT_UNVERIFIED_USERS_WHO_SUPPORT, + // user: u, + // trackId: `project-unverified-${ + // project.id + // }-${u.walletAddress?.toLowerCase()}-${now}`, + // }, + // })), + // ); + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_UNVERIFIED, + // sendEmail: true, + // segment: { + // analyticsUserId: user.segmentUserId(), + // anonymousId: user.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `project-unverified-${ + // project.id + // }-${user.walletAddress?.toLowerCase()}-${now}`, + // }); } async verificationFormRejected(params: { project: Project }): Promise { - const { project } = params; - const user = project.adminUser as User; - const now = Date.now(); - - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.VERIFICATION_FORM_REJECTED, - sendEmail: true, - segment: { - analyticsUserId: user.segmentUserId(), - anonymousId: user.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `verification-form-rejected-${ - project.id - }-${user.walletAddress?.toLowerCase()}-${now}`, - }); + return; + // const { project } = params; + // const user = project.adminUser as User; + // const now = Date.now(); + + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.VERIFICATION_FORM_REJECTED, + // sendEmail: true, + // segment: { + // analyticsUserId: user.segmentUserId(), + // anonymousId: user.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `verification-form-rejected-${ + // project.id + // }-${user.walletAddress?.toLowerCase()}-${now}`, + // }); } async projectReceivedHeartReaction(params: { project: Project; userId: number; }): Promise { - const { project } = params; - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_RECEIVED_HEART, - - // With adding trackId to notification, notification-center would not create new notification - // If there is already a notification with this trackId in DB - trackId: generateTrackId({ - userId: params.userId, - projectId: project?.id as number, - action: 'likeProject', - }), - }); + return; + // const { project } = params; + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_RECEIVED_HEART, + + // // With adding trackId to notification, notification-center would not create new notification + // // If there is already a notification with this trackId in DB + // trackId: generateTrackId({ + // userId: params.userId, + // projectId: project?.id as number, + // action: 'likeProject', + // }), + // }); } ProfileIsCompleted(params: { user: User }): Promise { @@ -430,433 +443,446 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface { } async projectCancelled(params: { project: Project }): Promise { - const { project } = params; - const now = Date.now(); - - const supporters = await findUsersWhoSupportProject(project.id); - await sendProjectRelatedNotificationsQueue.addBulk( - supporters.map(user => ({ - data: { - project, - eventName: - NOTIFICATIONS_EVENT_NAMES.PROJECT_CANCELLED_USERS_WHO_SUPPORT, - user, - trackId: `project-cancelled-${ - project.id - }-${user.walletAddress?.toLowerCase()}-${now}`, - }, - })), - ); - - const projectOwner = project?.adminUser as User; - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_CANCELLED, - sendEmail: true, - segment: { - analyticsUserId: projectOwner.segmentUserId(), - anonymousId: projectOwner.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `project-cancelled-${ - project.id - }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - }); + return; + // const { project } = params; + // const now = Date.now(); + + // const supporters = await findUsersWhoSupportProject(project.id); + // await sendProjectRelatedNotificationsQueue.addBulk( + // supporters.map(user => ({ + // data: { + // project, + // eventName: + // NOTIFICATIONS_EVENT_NAMES.PROJECT_CANCELLED_USERS_WHO_SUPPORT, + // user, + // trackId: `project-cancelled-${ + // project.id + // }-${user.walletAddress?.toLowerCase()}-${now}`, + // }, + // })), + // ); + + // const projectOwner = project?.adminUser as User; + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_CANCELLED, + // sendEmail: true, + // segment: { + // analyticsUserId: projectOwner.segmentUserId(), + // anonymousId: projectOwner.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `project-cancelled-${ + // project.id + // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + // }); } async projectUpdateAdded(params: { project: Project; update: string; }): Promise { - const { project, update } = params; - const now = Date.now(); - - const supporters = await findUsersWhoSupportProject(project.id); - await sendProjectRelatedNotificationsQueue.addBulk( - supporters.map(user => ({ - data: { - project, - eventName: - NOTIFICATIONS_EVENT_NAMES.PROJECT_ADD_AN_UPDATE_USERS_WHO_SUPPORT, - user, - trackId: `project-update-added-${ - project.id - }-${user.walletAddress?.toLowerCase()}-${now}`, - }, - })), - ); - - const projectOwner = project?.adminUser as User; - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_UPDATE_ADDED_OWNER, - sendEmail: true, - segment: { - analyticsUserId: projectOwner.segmentUserId(), - anonymousId: projectOwner.segmentUserId(), - payload: { - ...getSegmentProjectAttributes({ - project, - }), - update, - }, - }, - trackId: `project-update-added-${ - project.id - }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - }); + return; + // const { project, update } = params; + // const now = Date.now(); + + // const supporters = await findUsersWhoSupportProject(project.id); + // await sendProjectRelatedNotificationsQueue.addBulk( + // supporters.map(user => ({ + // data: { + // project, + // eventName: + // NOTIFICATIONS_EVENT_NAMES.PROJECT_ADD_AN_UPDATE_USERS_WHO_SUPPORT, + // user, + // trackId: `project-update-added-${ + // project.id + // }-${user.walletAddress?.toLowerCase()}-${now}`, + // }, + // })), + // ); + + // const projectOwner = project?.adminUser as User; + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_UPDATE_ADDED_OWNER, + // sendEmail: true, + // segment: { + // analyticsUserId: projectOwner.segmentUserId(), + // anonymousId: projectOwner.segmentUserId(), + // payload: { + // ...getSegmentProjectAttributes({ + // project, + // }), + // update, + // }, + // }, + // trackId: `project-update-added-${ + // project.id + // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + // }); } async projectDeListed(params: { project: Project }): Promise { - const { project } = params; - const now = Date.now(); - - const supporters = await findUsersWhoSupportProject(project.id); - await sendProjectRelatedNotificationsQueue.addBulk( - supporters.map(user => ({ - data: { - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_UNLISTED_SUPPORTED, - user, - trackId: `project-unlisted-${ - project.id - }-${user.walletAddress?.toLowerCase()}-${now}`, - }, - })), - ); - - const projectOwner = project?.adminUser as User; - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_UNLISTED, - - sendEmail: true, - segment: { - analyticsUserId: projectOwner.segmentUserId(), - anonymousId: projectOwner.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `project-unlisted-${ - project.id - }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - }); + return; + // const { project } = params; + // const now = Date.now(); + + // const supporters = await findUsersWhoSupportProject(project.id); + // await sendProjectRelatedNotificationsQueue.addBulk( + // supporters.map(user => ({ + // data: { + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_UNLISTED_SUPPORTED, + // user, + // trackId: `project-unlisted-${ + // project.id + // }-${user.walletAddress?.toLowerCase()}-${now}`, + // }, + // })), + // ); + + // const projectOwner = project?.adminUser as User; + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_UNLISTED, + + // sendEmail: true, + // segment: { + // analyticsUserId: projectOwner.segmentUserId(), + // anonymousId: projectOwner.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `project-unlisted-${ + // project.id + // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + // }); } async projectDeactivated(params: { project: Project; reason?: string; }): Promise { - const { project, reason } = params; - const metadata = { - reason, - }; - const now = Date.now(); - - const projectOwner = project?.adminUser as User; - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_DEACTIVATED, - metadata, - - sendEmail: true, - segment: { - analyticsUserId: projectOwner.segmentUserId(), - anonymousId: projectOwner.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `project-deactivated-${ - project.id - }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - }); - - const supporters = await findUsersWhoSupportProject(project.id); - await sendProjectRelatedNotificationsQueue.addBulk( - supporters.map(user => ({ - data: { - project, - eventName: - NOTIFICATIONS_EVENT_NAMES.PROJECT_DEACTIVATED_USERS_WHO_SUPPORT, - user, - metadata, - trackId: `project-deactivated-${ - project.id - }-${user.walletAddress?.toLowerCase()}-${now}`, - }, - })), - ); + return; + // const { project, reason } = params; + // const metadata = { + // reason, + // }; + // const now = Date.now(); + + // const projectOwner = project?.adminUser as User; + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_DEACTIVATED, + // metadata, + + // sendEmail: true, + // segment: { + // analyticsUserId: projectOwner.segmentUserId(), + // anonymousId: projectOwner.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `project-deactivated-${ + // project.id + // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + // }); + + // const supporters = await findUsersWhoSupportProject(project.id); + // await sendProjectRelatedNotificationsQueue.addBulk( + // supporters.map(user => ({ + // data: { + // project, + // eventName: + // NOTIFICATIONS_EVENT_NAMES.PROJECT_DEACTIVATED_USERS_WHO_SUPPORT, + // user, + // metadata, + // trackId: `project-deactivated-${ + // project.id + // }-${user.walletAddress?.toLowerCase()}-${now}`, + // }, + // })), + // ); } async projectListed(params: { project: Project }): Promise { - const { project } = params; - const projectOwner = project?.adminUser as User; - const now = Date.now(); - - const supporters = await findUsersWhoSupportProject(project.id); - await sendProjectRelatedNotificationsQueue.addBulk( - supporters.map(user => ({ - data: { - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_LISTED_SUPPORTED, - user, - trackId: `project-listed-${ - project.id - }-${user.walletAddress?.toLowerCase()}-${now}`, - }, - })), - ); - - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_LISTED, - - sendEmail: true, - segment: { - analyticsUserId: projectOwner.segmentUserId(), - anonymousId: projectOwner.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `project-listed-${ - project.id - }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - }); + return; + // const { project } = params; + // const projectOwner = project?.adminUser as User; + // const now = Date.now(); + + // const supporters = await findUsersWhoSupportProject(project.id); + // await sendProjectRelatedNotificationsQueue.addBulk( + // supporters.map(user => ({ + // data: { + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_LISTED_SUPPORTED, + // user, + // trackId: `project-listed-${ + // project.id + // }-${user.walletAddress?.toLowerCase()}-${now}`, + // }, + // })), + // ); + + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_LISTED, + + // sendEmail: true, + // segment: { + // analyticsUserId: projectOwner.segmentUserId(), + // anonymousId: projectOwner.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `project-listed-${ + // project.id + // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + // }); } async projectEdited(params: { project: Project }): Promise { - const { project } = params; - const projectOwner = project?.adminUser as User; - const now = Date.now(); - - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_EDITED, - - sendEmail: true, - segment: { - analyticsUserId: projectOwner.segmentUserId(), - anonymousId: projectOwner.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `project-edited-${ - project.id - }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - }); + return; + // const { project } = params; + // const projectOwner = project?.adminUser as User; + // const now = Date.now(); + + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_EDITED, + + // sendEmail: true, + // segment: { + // analyticsUserId: projectOwner.segmentUserId(), + // anonymousId: projectOwner.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `project-edited-${ + // project.id + // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + // }); } async projectGotDraftByAdmin(params: { project: Project }): Promise { - const { project } = params; - const projectOwner = project?.adminUser as User; - const now = Date.now(); - - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.VERIFICATION_FORM_GOT_DRAFT_BY_ADMIN, - sendEmail: true, - segment: { - analyticsUserId: projectOwner.segmentUserId(), - anonymousId: projectOwner.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `project-got-draft-by-admin-${ - project.id - }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - }); + return; + // const { project } = params; + // const projectOwner = project?.adminUser as User; + // const now = Date.now(); + + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.VERIFICATION_FORM_GOT_DRAFT_BY_ADMIN, + // sendEmail: true, + // segment: { + // analyticsUserId: projectOwner.segmentUserId(), + // anonymousId: projectOwner.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `project-got-draft-by-admin-${ + // project.id + // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + // }); } async projectPublished(params: { project: Project }): Promise { - const { project } = params; - const projectOwner = project?.adminUser as User; - const now = Date.now(); - - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.DRAFTED_PROJECT_ACTIVATED, - - sendEmail: true, - segment: { - analyticsUserId: projectOwner.segmentUserId(), - anonymousId: projectOwner.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `project-published-${ - project.id - }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - }); + return; + // const { project } = params; + // const projectOwner = project?.adminUser as User; + // const now = Date.now(); + + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.DRAFTED_PROJECT_ACTIVATED, + + // sendEmail: true, + // segment: { + // analyticsUserId: projectOwner.segmentUserId(), + // anonymousId: projectOwner.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `project-published-${ + // project.id + // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + // }); } async projectReactivated(params: { project: Project }): Promise { - const { project } = params; - const now = Date.now(); - const supporters = await findUsersWhoSupportProject(project.id); - await sendProjectRelatedNotificationsQueue.addBulk( - supporters.map(user => ({ - data: { - project, - eventName: - NOTIFICATIONS_EVENT_NAMES.PROJECT_ACTIVATED_USERS_WHO_SUPPORT, - user, - trackId: `project-reactivated-${ - project.id - }-${user.walletAddress.toLowerCase()}-${now}`, - }, - })), - ); + return; + // const { project } = params; + // const now = Date.now(); + // const supporters = await findUsersWhoSupportProject(project.id); + // await sendProjectRelatedNotificationsQueue.addBulk( + // supporters.map(user => ({ + // data: { + // project, + // eventName: + // NOTIFICATIONS_EVENT_NAMES.PROJECT_ACTIVATED_USERS_WHO_SUPPORT, + // user, + // trackId: `project-reactivated-${ + // project.id + // }-${user.walletAddress.toLowerCase()}-${now}`, + // }, + // })), + // ); } async projectSavedAsDraft(params: { project: Project }): Promise { - const { project } = params; - const projectOwner = project?.adminUser as User; - const now = Date.now(); - - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_CREATED, - - sendEmail: true, - segment: { - analyticsUserId: projectOwner.segmentUserId(), - anonymousId: projectOwner.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `project-saved-as-draft-${ - project.id - }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - }); + return; + // const { project } = params; + // const projectOwner = project?.adminUser as User; + // const now = Date.now(); + + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_CREATED, + + // sendEmail: true, + // segment: { + // analyticsUserId: projectOwner.segmentUserId(), + // anonymousId: projectOwner.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `project-saved-as-draft-${ + // project.id + // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + // }); } async donationGetPriceFailed(params: { project: Project; donationInfo: { txLink: string; reason: string }; }): Promise { - const { project, donationInfo } = params; - const { txLink, reason } = donationInfo; - const projectOwner = project?.adminUser as User; - const now = Date.now(); - - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.DONATION_GET_PRICE_FAILED, - metadata: { - txLink, - reason, - }, - sendEmail: false, - segment: { - analyticsUserId: projectOwner.segmentUserId(), - anonymousId: projectOwner.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `donation-get-price-failed-${project.id}-${donationInfo.txLink}-${now}`, - }); + return; + // const { project, donationInfo } = params; + // const { txLink, reason } = donationInfo; + // const projectOwner = project?.adminUser as User; + // const now = Date.now(); + + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.DONATION_GET_PRICE_FAILED, + // metadata: { + // txLink, + // reason, + // }, + // sendEmail: false, + // segment: { + // analyticsUserId: projectOwner.segmentUserId(), + // anonymousId: projectOwner.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `donation-get-price-failed-${project.id}-${donationInfo.txLink}-${now}`, + // }); } async broadcastNotification( params: BroadCastNotificationInputParams, ): Promise { - const { html, broadCastNotificationId } = params; - let allUserFetched = false; - const take = 100; - let skip = 0; - const trackIdPrefix = `broadCast-${broadCastNotificationId}`; - while (!allUserFetched) { - const { users } = await findAllUsers({ take, skip }); - if (users.length === 0) { - allUserFetched = true; - break; - } - skip += users.length; - const queueData: SendBatchNotificationBody = { notifications: [] }; - for (const user of users) { - // with adding .toLowerCase() to wallet address we make sure if two wallet address with different case - // exist we would set same trackId for them - const trackId = `${trackIdPrefix}-${user.walletAddress?.toLowerCase()}`; - if ( - queueData.notifications.find( - notificationData => notificationData.trackId === trackId, - ) - ) { - // We should not have items with repetitive trackIds in sending bulk notifications - // and we may have some users with same wallet address, so we need to add this checking - // https://github.com/Giveth/giveth-dapps-v2/issues/2084 - continue; - } - queueData.notifications.push({ - email: user.email as string, - eventName: NOTIFICATIONS_EVENT_NAMES.RAW_HTML_BROADCAST, - sendEmail: false, - sendSegment: false, - metadata: { - html, - }, - userWalletAddress: user.walletAddress as string, - trackId, - }); - } - sendBroadcastNotificationsQueue.add(queueData); - } + return; + // const { html, broadCastNotificationId } = params; + // let allUserFetched = false; + // const take = 100; + // let skip = 0; + // const trackIdPrefix = `broadCast-${broadCastNotificationId}`; + // while (!allUserFetched) { + // const { users } = await findAllUsers({ take, skip }); + // if (users.length === 0) { + // allUserFetched = true; + // break; + // } + // skip += users.length; + // const queueData: SendBatchNotificationBody = { notifications: [] }; + // for (const user of users) { + // // with adding .toLowerCase() to wallet address we make sure if two wallet address with different case + // // exist we would set same trackId for them + // const trackId = `${trackIdPrefix}-${user.walletAddress?.toLowerCase()}`; + // if ( + // queueData.notifications.find( + // notificationData => notificationData.trackId === trackId, + // ) + // ) { + // // We should not have items with repetitive trackIds in sending bulk notifications + // // and we may have some users with same wallet address, so we need to add this checking + // // https://github.com/Giveth/giveth-dapps-v2/issues/2084 + // continue; + // } + // queueData.notifications.push({ + // email: user.email as string, + // eventName: NOTIFICATIONS_EVENT_NAMES.RAW_HTML_BROADCAST, + // sendEmail: false, + // sendSegment: false, + // metadata: { + // html, + // }, + // userWalletAddress: user.walletAddress as string, + // trackId, + // }); + // } + // sendBroadcastNotificationsQueue.add(queueData); + // } } async projectsHaveNewRank(params: ProjectsHaveNewRankingInputParam) { - for (const param of params.projectRanks) { - const project = await findProjectById(param.projectId); - if (!project) { - continue; - } - const projectOwner = project.adminUser; - let eventName; - - // https://github.com/Giveth/impact-graph/issues/774#issuecomment-1542337083 - if ( - param.oldRank === params.oldBottomRank && - param.newRank === params.newBottomRank - ) { - // We dont send any notification in this case, because project has no givPower so rank change doesnt matter - continue; - } else if (param.oldRank === params.oldBottomRank) { - eventName = NOTIFICATIONS_EVENT_NAMES.YOUR_PROJECT_GOT_A_RANK; - } else if (param.newRank < param.oldRank) { - eventName = NOTIFICATIONS_EVENT_NAMES.PROJECT_HAS_RISEN_IN_THE_RANK; - } else if (param.newRank > param.oldRank) { - eventName = NOTIFICATIONS_EVENT_NAMES.PROJECT_HAS_A_NEW_RANK; - } - logger.info('send rank changed notification ', { - eventName, - slug: project.slug, - newRank: param.newRank, - oldRank: param.oldRank, - oldBottomRank: params.oldBottomRank, - newBottomRank: params.newBottomRank, - }); - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName, - sendEmail: true, - segment: { - analyticsUserId: projectOwner.segmentUserId(), - anonymousId: projectOwner.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `project-has-new-rank-${param.round}-${param.projectId}`, - }); - } + return; + // for (const param of params.projectRanks) { + // const project = await findProjectById(param.projectId); + // if (!project) { + // continue; + // } + // const projectOwner = project.adminUser; + // let eventName; + + // // https://github.com/Giveth/impact-graph/issues/774#issuecomment-1542337083 + // if ( + // param.oldRank === params.oldBottomRank && + // param.newRank === params.newBottomRank + // ) { + // // We dont send any notification in this case, because project has no givPower so rank change doesnt matter + // continue; + // } else if (param.oldRank === params.oldBottomRank) { + // eventName = NOTIFICATIONS_EVENT_NAMES.YOUR_PROJECT_GOT_A_RANK; + // } else if (param.newRank < param.oldRank) { + // eventName = NOTIFICATIONS_EVENT_NAMES.PROJECT_HAS_RISEN_IN_THE_RANK; + // } else if (param.newRank > param.oldRank) { + // eventName = NOTIFICATIONS_EVENT_NAMES.PROJECT_HAS_A_NEW_RANK; + // } + // logger.info('send rank changed notification ', { + // eventName, + // slug: project.slug, + // newRank: param.newRank, + // oldRank: param.oldRank, + // oldBottomRank: params.oldBottomRank, + // newBottomRank: params.newBottomRank, + // }); + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName, + // sendEmail: true, + // segment: { + // analyticsUserId: projectOwner.segmentUserId(), + // anonymousId: projectOwner.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `project-has-new-rank-${param.round}-${param.projectId}`, + // }); + // } } } From dadb84779c86a4bb5434690087048ef7fd93d856 Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Tue, 16 Jan 2024 19:57:08 +0330 Subject: [PATCH 19/34] Revert "comment out notification center methods interface" This reverts commit 4a6dd8c4082fceee0b822c1f14a9c0a0b6968d41. --- .../NotificationCenterAdapter.ts | 1348 ++++++++--------- 1 file changed, 661 insertions(+), 687 deletions(-) diff --git a/src/adapters/notifications/NotificationCenterAdapter.ts b/src/adapters/notifications/NotificationCenterAdapter.ts index ef6c510aa..939bdc5c9 100644 --- a/src/adapters/notifications/NotificationCenterAdapter.ts +++ b/src/adapters/notifications/NotificationCenterAdapter.ts @@ -86,28 +86,27 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface { donation: Donation; project: Project; }): Promise { - return; - // const { project, donation } = params; - // const user = project.adminUser as User; - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.DONATION_RECEIVED, - // sendEmail: true, - // segment: { - // analyticsUserId: user.segmentUserId(), - // anonymousId: user.segmentUserId(), - // payload: getSegmentDonationAttributes({ - // donation, - // project, - // user, - // }), - // }, - // trackId: - // 'donation-received-' + - // donation.transactionNetworkId + - // '-' + - // donation.transactionId, - // }); + const { project, donation } = params; + const user = project.adminUser as User; + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.DONATION_RECEIVED, + sendEmail: true, + segment: { + analyticsUserId: user.segmentUserId(), + anonymousId: user.segmentUserId(), + payload: getSegmentDonationAttributes({ + donation, + project, + user, + }), + }, + trackId: + 'donation-received-' + + donation.transactionNetworkId + + '-' + + donation.transactionId, + }); } async donationSent(params: { @@ -115,323 +114,311 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface { project: Project; donor: User; }): Promise { - return; - // const { project, donor, donation } = params; - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.MADE_DONATION, - // user: { - // email: donor.email, - // walletAddress: donor.walletAddress as string, - // }, - // sendEmail: true, - // segment: { - // analyticsUserId: donor.segmentUserId(), - // anonymousId: donor.segmentUserId(), - // payload: { - // ...getSegmentDonationAttributes({ - // donation, - // project, - // user: donor, - // }), - - // // We just want this to be donation sent event not made donation, so don put it in getSegmentDonationAttributes() - // // see https://github.com/Giveth/impact-graph/pull/716 - // fromWalletAddress: donation.fromWalletAddress.toLowerCase(), - // }, - // }, - // trackId: - // 'donation-sent-' + - // donation.transactionNetworkId + - // '-' + - // donation.transactionId, - // }); + const { project, donor, donation } = params; + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.MADE_DONATION, + user: { + email: donor.email, + walletAddress: donor.walletAddress as string, + }, + sendEmail: true, + segment: { + analyticsUserId: donor.segmentUserId(), + anonymousId: donor.segmentUserId(), + payload: { + ...getSegmentDonationAttributes({ + donation, + project, + user: donor, + }), + + // We just want this to be donation sent event not made donation, so don put it in getSegmentDonationAttributes() + // see https://github.com/Giveth/impact-graph/pull/716 + fromWalletAddress: donation.fromWalletAddress.toLowerCase(), + }, + }, + trackId: + 'donation-sent-' + + donation.transactionNetworkId + + '-' + + donation.transactionId, + }); } async projectVerified(params: { project: Project }): Promise { - return; - // const { project } = params; - // const projectOwner = project.adminUser as User; - // const now = new Date(); - - // const supporters = await findUsersWhoSupportProject(project.id); - // await sendProjectRelatedNotificationsQueue.addBulk( - // supporters.map(user => ({ - // data: { - // project, - // eventName: - // NOTIFICATIONS_EVENT_NAMES.PROJECT_VERIFIED_USERS_WHO_SUPPORT, - // user, - // trackId: `project-verified-${ - // project.id - // }-${user.walletAddress.toLowerCase()}-${now}`, - // }, - // })), - // ); - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_VERIFIED, - // sendEmail: true, - // segment: { - // analyticsUserId: projectOwner.segmentUserId(), - // anonymousId: projectOwner.segmentUserId(), - // payload: getSegmentProjectAttributes({ - // project, - // }), - // }, - // trackId: `project-verified-${ - // project.id - // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - // }); + const { project } = params; + const projectOwner = project.adminUser as User; + const now = new Date(); + + const supporters = await findUsersWhoSupportProject(project.id); + await sendProjectRelatedNotificationsQueue.addBulk( + supporters.map(user => ({ + data: { + project, + eventName: + NOTIFICATIONS_EVENT_NAMES.PROJECT_VERIFIED_USERS_WHO_SUPPORT, + user, + trackId: `project-verified-${ + project.id + }-${user.walletAddress.toLowerCase()}-${now}`, + }, + })), + ); + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_VERIFIED, + sendEmail: true, + segment: { + analyticsUserId: projectOwner.segmentUserId(), + anonymousId: projectOwner.segmentUserId(), + payload: getSegmentProjectAttributes({ + project, + }), + }, + trackId: `project-verified-${ + project.id + }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + }); } async projectBoosted(params: { projectId: number; userId: number; }): Promise { - return; - // const { projectId, userId } = params; - // const project = (await findProjectById(projectId)) as Project; - // sendProjectRelatedNotificationsQueue.add({ - // project: project as Project, - // eventName: - // project.adminUser?.id === userId - // ? // We send different notifications when project owner or someone else boost the project https://github.com/Giveth/notification-center/issues/41 - // NOTIFICATIONS_EVENT_NAMES.PROJECT_BOOSTED_BY_PROJECT_OWNER - // : NOTIFICATIONS_EVENT_NAMES.PROJECT_BOOSTED, - - // // With adding trackId to notification, notification-center would not create new notification - // // If there is already a notification with this trackId in DB - // trackId: generateTrackId({ - // userId, - // projectId: project?.id as number, - // action: 'boostProject', - // }), - // }); + const { projectId, userId } = params; + const project = (await findProjectById(projectId)) as Project; + sendProjectRelatedNotificationsQueue.add({ + project: project as Project, + eventName: + project.adminUser?.id === userId + ? // We send different notifications when project owner or someone else boost the project https://github.com/Giveth/notification-center/issues/41 + NOTIFICATIONS_EVENT_NAMES.PROJECT_BOOSTED_BY_PROJECT_OWNER + : NOTIFICATIONS_EVENT_NAMES.PROJECT_BOOSTED, + + // With adding trackId to notification, notification-center would not create new notification + // If there is already a notification with this trackId in DB + trackId: generateTrackId({ + userId, + projectId: project?.id as number, + action: 'boostProject', + }), + }); } async projectBoostedBatch(params: { projectIds: number[]; userId: number; }): Promise { - return; - // const { userId, projectIds } = params; - // for (const projectId of projectIds) { - // await this.projectBoosted({ - // userId, - // projectId, - // }); - // } + const { userId, projectIds } = params; + for (const projectId of projectIds) { + await this.projectBoosted({ + userId, + projectId, + }); + } } async projectBadgeRevoked(params: { project: Project }): Promise { - return; - // const { project } = params; - // const user = project.adminUser as User; - // const supporters = await findUsersWhoSupportProject(project.id); - // const now = new Date(); - // await sendProjectRelatedNotificationsQueue.addBulk( - // supporters.map(u => ({ - // data: { - // project, - // eventName: - // NOTIFICATIONS_EVENT_NAMES.PROJECT_UNVERIFIED_USERS_WHO_SUPPORT, - // user: u, - // trackId: `project-unverified-${ - // project.id - // }-${u.walletAddress.toLowerCase()}-${now}}`, - // }, - // })), - // ); - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKED, - // sendEmail: true, - // segment: { - // analyticsUserId: user.segmentUserId(), - // anonymousId: user.segmentUserId(), - // payload: getSegmentProjectAttributes({ - // project, - // }), - // }, - // trackId: `project-badge-revoked-${ - // project.id - // }-${user.walletAddress?.toLowerCase()}-${now}`, - // }); + const { project } = params; + const user = project.adminUser as User; + const supporters = await findUsersWhoSupportProject(project.id); + const now = new Date(); + await sendProjectRelatedNotificationsQueue.addBulk( + supporters.map(u => ({ + data: { + project, + eventName: + NOTIFICATIONS_EVENT_NAMES.PROJECT_UNVERIFIED_USERS_WHO_SUPPORT, + user: u, + trackId: `project-unverified-${ + project.id + }-${u.walletAddress.toLowerCase()}-${now}}`, + }, + })), + ); + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKED, + sendEmail: true, + segment: { + analyticsUserId: user.segmentUserId(), + anonymousId: user.segmentUserId(), + payload: getSegmentProjectAttributes({ + project, + }), + }, + trackId: `project-badge-revoked-${ + project.id + }-${user.walletAddress?.toLowerCase()}-${now}`, + }); } async projectBadgeRevokeReminder(params: { project: Project; }): Promise { - return; - // const { project } = params; - // const user = project.adminUser as User; - // const now = new Date(); - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKE_REMINDER, - // sendEmail: true, - // segment: { - // analyticsUserId: user.segmentUserId(), - // anonymousId: user.segmentUserId(), - // payload: getSegmentProjectAttributes({ - // project, - // }), - // }, - // trackId: `project-badge-revoke-reminder-${ - // project.id - // }-${user.walletAddress?.toLowerCase()}-${now}`, - // }); + const { project } = params; + const user = project.adminUser as User; + const now = new Date(); + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKE_REMINDER, + sendEmail: true, + segment: { + analyticsUserId: user.segmentUserId(), + anonymousId: user.segmentUserId(), + payload: getSegmentProjectAttributes({ + project, + }), + }, + trackId: `project-badge-revoke-reminder-${ + project.id + }-${user.walletAddress?.toLowerCase()}-${now}`, + }); } async projectBadgeRevokeWarning(params: { project: Project }): Promise { - return; - // const { project } = params; - // const user = project.adminUser as User; - // const now = new Date(); - - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKE_WARNING, - // sendEmail: true, - // segment: { - // analyticsUserId: user.segmentUserId(), - // anonymousId: user.segmentUserId(), - // payload: getSegmentProjectAttributes({ - // project, - // }), - // }, - // trackId: `project-badge-revoke-warning-${ - // project.id - // }-${user.walletAddress?.toLowerCase()}-${now}`, - // }); + const { project } = params; + const user = project.adminUser as User; + const now = new Date(); + + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKE_WARNING, + sendEmail: true, + segment: { + analyticsUserId: user.segmentUserId(), + anonymousId: user.segmentUserId(), + payload: getSegmentProjectAttributes({ + project, + }), + }, + trackId: `project-badge-revoke-warning-${ + project.id + }-${user.walletAddress?.toLowerCase()}-${now}`, + }); } async projectBadgeRevokeLastWarning(params: { project: Project; }): Promise { - return; - // const { project } = params; - // const user = project.adminUser as User; - // const now = Date.now(); - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKE_LAST_WARNING, - // sendEmail: true, - // segment: { - // analyticsUserId: user.segmentUserId(), - // anonymousId: user.segmentUserId(), - // payload: getSegmentProjectAttributes({ - // project, - // }), - // }, - // trackId: `project-badge-revoke-last-warning-${ - // project.id - // }-${user.walletAddress?.toLowerCase()}-${now}`, - // }); + const { project } = params; + const user = project.adminUser as User; + const now = Date.now(); + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_REVOKE_LAST_WARNING, + sendEmail: true, + segment: { + analyticsUserId: user.segmentUserId(), + anonymousId: user.segmentUserId(), + payload: getSegmentProjectAttributes({ + project, + }), + }, + trackId: `project-badge-revoke-last-warning-${ + project.id + }-${user.walletAddress?.toLowerCase()}-${now}`, + }); } async projectBadgeUpForRevoking(params: { project: Project }): Promise { - return; - // const { project } = params; - // const user = project.adminUser as User; - // const now = Date.now(); - - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_UP_FOR_REVOKING, - // sendEmail: true, - // segment: { - // analyticsUserId: user.segmentUserId(), - // anonymousId: user.segmentUserId(), - // payload: getSegmentProjectAttributes({ - // project, - // }), - // }, - // trackId: `project-badge-up-for-revoking-${ - // project.id - // }-${user.walletAddress?.toLowerCase()}-${now}`, - // }); + const { project } = params; + const user = project.adminUser as User; + const now = Date.now(); + + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_BADGE_UP_FOR_REVOKING, + sendEmail: true, + segment: { + analyticsUserId: user.segmentUserId(), + anonymousId: user.segmentUserId(), + payload: getSegmentProjectAttributes({ + project, + }), + }, + trackId: `project-badge-up-for-revoking-${ + project.id + }-${user.walletAddress?.toLowerCase()}-${now}`, + }); } async projectUnVerified(params: { project: Project }): Promise { - return; - // const { project } = params; - // const user = project.adminUser as User; - // const now = Date.now(); - - // const supporters = await findUsersWhoSupportProject(project.id); - // await sendProjectRelatedNotificationsQueue.addBulk( - // supporters.map(u => ({ - // data: { - // project, - // eventName: - // NOTIFICATIONS_EVENT_NAMES.PROJECT_UNVERIFIED_USERS_WHO_SUPPORT, - // user: u, - // trackId: `project-unverified-${ - // project.id - // }-${u.walletAddress?.toLowerCase()}-${now}`, - // }, - // })), - // ); - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_UNVERIFIED, - // sendEmail: true, - // segment: { - // analyticsUserId: user.segmentUserId(), - // anonymousId: user.segmentUserId(), - // payload: getSegmentProjectAttributes({ - // project, - // }), - // }, - // trackId: `project-unverified-${ - // project.id - // }-${user.walletAddress?.toLowerCase()}-${now}`, - // }); + const { project } = params; + const user = project.adminUser as User; + const now = Date.now(); + + const supporters = await findUsersWhoSupportProject(project.id); + await sendProjectRelatedNotificationsQueue.addBulk( + supporters.map(u => ({ + data: { + project, + eventName: + NOTIFICATIONS_EVENT_NAMES.PROJECT_UNVERIFIED_USERS_WHO_SUPPORT, + user: u, + trackId: `project-unverified-${ + project.id + }-${u.walletAddress?.toLowerCase()}-${now}`, + }, + })), + ); + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_UNVERIFIED, + sendEmail: true, + segment: { + analyticsUserId: user.segmentUserId(), + anonymousId: user.segmentUserId(), + payload: getSegmentProjectAttributes({ + project, + }), + }, + trackId: `project-unverified-${ + project.id + }-${user.walletAddress?.toLowerCase()}-${now}`, + }); } async verificationFormRejected(params: { project: Project }): Promise { - return; - // const { project } = params; - // const user = project.adminUser as User; - // const now = Date.now(); - - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.VERIFICATION_FORM_REJECTED, - // sendEmail: true, - // segment: { - // analyticsUserId: user.segmentUserId(), - // anonymousId: user.segmentUserId(), - // payload: getSegmentProjectAttributes({ - // project, - // }), - // }, - // trackId: `verification-form-rejected-${ - // project.id - // }-${user.walletAddress?.toLowerCase()}-${now}`, - // }); + const { project } = params; + const user = project.adminUser as User; + const now = Date.now(); + + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.VERIFICATION_FORM_REJECTED, + sendEmail: true, + segment: { + analyticsUserId: user.segmentUserId(), + anonymousId: user.segmentUserId(), + payload: getSegmentProjectAttributes({ + project, + }), + }, + trackId: `verification-form-rejected-${ + project.id + }-${user.walletAddress?.toLowerCase()}-${now}`, + }); } async projectReceivedHeartReaction(params: { project: Project; userId: number; }): Promise { - return; - // const { project } = params; - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_RECEIVED_HEART, - - // // With adding trackId to notification, notification-center would not create new notification - // // If there is already a notification with this trackId in DB - // trackId: generateTrackId({ - // userId: params.userId, - // projectId: project?.id as number, - // action: 'likeProject', - // }), - // }); + const { project } = params; + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_RECEIVED_HEART, + + // With adding trackId to notification, notification-center would not create new notification + // If there is already a notification with this trackId in DB + trackId: generateTrackId({ + userId: params.userId, + projectId: project?.id as number, + action: 'likeProject', + }), + }); } ProfileIsCompleted(params: { user: User }): Promise { @@ -443,446 +430,433 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface { } async projectCancelled(params: { project: Project }): Promise { - return; - // const { project } = params; - // const now = Date.now(); - - // const supporters = await findUsersWhoSupportProject(project.id); - // await sendProjectRelatedNotificationsQueue.addBulk( - // supporters.map(user => ({ - // data: { - // project, - // eventName: - // NOTIFICATIONS_EVENT_NAMES.PROJECT_CANCELLED_USERS_WHO_SUPPORT, - // user, - // trackId: `project-cancelled-${ - // project.id - // }-${user.walletAddress?.toLowerCase()}-${now}`, - // }, - // })), - // ); - - // const projectOwner = project?.adminUser as User; - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_CANCELLED, - // sendEmail: true, - // segment: { - // analyticsUserId: projectOwner.segmentUserId(), - // anonymousId: projectOwner.segmentUserId(), - // payload: getSegmentProjectAttributes({ - // project, - // }), - // }, - // trackId: `project-cancelled-${ - // project.id - // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - // }); + const { project } = params; + const now = Date.now(); + + const supporters = await findUsersWhoSupportProject(project.id); + await sendProjectRelatedNotificationsQueue.addBulk( + supporters.map(user => ({ + data: { + project, + eventName: + NOTIFICATIONS_EVENT_NAMES.PROJECT_CANCELLED_USERS_WHO_SUPPORT, + user, + trackId: `project-cancelled-${ + project.id + }-${user.walletAddress?.toLowerCase()}-${now}`, + }, + })), + ); + + const projectOwner = project?.adminUser as User; + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_CANCELLED, + sendEmail: true, + segment: { + analyticsUserId: projectOwner.segmentUserId(), + anonymousId: projectOwner.segmentUserId(), + payload: getSegmentProjectAttributes({ + project, + }), + }, + trackId: `project-cancelled-${ + project.id + }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + }); } async projectUpdateAdded(params: { project: Project; update: string; }): Promise { - return; - // const { project, update } = params; - // const now = Date.now(); - - // const supporters = await findUsersWhoSupportProject(project.id); - // await sendProjectRelatedNotificationsQueue.addBulk( - // supporters.map(user => ({ - // data: { - // project, - // eventName: - // NOTIFICATIONS_EVENT_NAMES.PROJECT_ADD_AN_UPDATE_USERS_WHO_SUPPORT, - // user, - // trackId: `project-update-added-${ - // project.id - // }-${user.walletAddress?.toLowerCase()}-${now}`, - // }, - // })), - // ); - - // const projectOwner = project?.adminUser as User; - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_UPDATE_ADDED_OWNER, - // sendEmail: true, - // segment: { - // analyticsUserId: projectOwner.segmentUserId(), - // anonymousId: projectOwner.segmentUserId(), - // payload: { - // ...getSegmentProjectAttributes({ - // project, - // }), - // update, - // }, - // }, - // trackId: `project-update-added-${ - // project.id - // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - // }); + const { project, update } = params; + const now = Date.now(); + + const supporters = await findUsersWhoSupportProject(project.id); + await sendProjectRelatedNotificationsQueue.addBulk( + supporters.map(user => ({ + data: { + project, + eventName: + NOTIFICATIONS_EVENT_NAMES.PROJECT_ADD_AN_UPDATE_USERS_WHO_SUPPORT, + user, + trackId: `project-update-added-${ + project.id + }-${user.walletAddress?.toLowerCase()}-${now}`, + }, + })), + ); + + const projectOwner = project?.adminUser as User; + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_UPDATE_ADDED_OWNER, + sendEmail: true, + segment: { + analyticsUserId: projectOwner.segmentUserId(), + anonymousId: projectOwner.segmentUserId(), + payload: { + ...getSegmentProjectAttributes({ + project, + }), + update, + }, + }, + trackId: `project-update-added-${ + project.id + }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + }); } async projectDeListed(params: { project: Project }): Promise { - return; - // const { project } = params; - // const now = Date.now(); - - // const supporters = await findUsersWhoSupportProject(project.id); - // await sendProjectRelatedNotificationsQueue.addBulk( - // supporters.map(user => ({ - // data: { - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_UNLISTED_SUPPORTED, - // user, - // trackId: `project-unlisted-${ - // project.id - // }-${user.walletAddress?.toLowerCase()}-${now}`, - // }, - // })), - // ); - - // const projectOwner = project?.adminUser as User; - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_UNLISTED, - - // sendEmail: true, - // segment: { - // analyticsUserId: projectOwner.segmentUserId(), - // anonymousId: projectOwner.segmentUserId(), - // payload: getSegmentProjectAttributes({ - // project, - // }), - // }, - // trackId: `project-unlisted-${ - // project.id - // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - // }); + const { project } = params; + const now = Date.now(); + + const supporters = await findUsersWhoSupportProject(project.id); + await sendProjectRelatedNotificationsQueue.addBulk( + supporters.map(user => ({ + data: { + project, + eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_UNLISTED_SUPPORTED, + user, + trackId: `project-unlisted-${ + project.id + }-${user.walletAddress?.toLowerCase()}-${now}`, + }, + })), + ); + + const projectOwner = project?.adminUser as User; + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_UNLISTED, + + sendEmail: true, + segment: { + analyticsUserId: projectOwner.segmentUserId(), + anonymousId: projectOwner.segmentUserId(), + payload: getSegmentProjectAttributes({ + project, + }), + }, + trackId: `project-unlisted-${ + project.id + }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + }); } async projectDeactivated(params: { project: Project; reason?: string; }): Promise { - return; - // const { project, reason } = params; - // const metadata = { - // reason, - // }; - // const now = Date.now(); - - // const projectOwner = project?.adminUser as User; - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_DEACTIVATED, - // metadata, - - // sendEmail: true, - // segment: { - // analyticsUserId: projectOwner.segmentUserId(), - // anonymousId: projectOwner.segmentUserId(), - // payload: getSegmentProjectAttributes({ - // project, - // }), - // }, - // trackId: `project-deactivated-${ - // project.id - // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - // }); - - // const supporters = await findUsersWhoSupportProject(project.id); - // await sendProjectRelatedNotificationsQueue.addBulk( - // supporters.map(user => ({ - // data: { - // project, - // eventName: - // NOTIFICATIONS_EVENT_NAMES.PROJECT_DEACTIVATED_USERS_WHO_SUPPORT, - // user, - // metadata, - // trackId: `project-deactivated-${ - // project.id - // }-${user.walletAddress?.toLowerCase()}-${now}`, - // }, - // })), - // ); + const { project, reason } = params; + const metadata = { + reason, + }; + const now = Date.now(); + + const projectOwner = project?.adminUser as User; + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_DEACTIVATED, + metadata, + + sendEmail: true, + segment: { + analyticsUserId: projectOwner.segmentUserId(), + anonymousId: projectOwner.segmentUserId(), + payload: getSegmentProjectAttributes({ + project, + }), + }, + trackId: `project-deactivated-${ + project.id + }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + }); + + const supporters = await findUsersWhoSupportProject(project.id); + await sendProjectRelatedNotificationsQueue.addBulk( + supporters.map(user => ({ + data: { + project, + eventName: + NOTIFICATIONS_EVENT_NAMES.PROJECT_DEACTIVATED_USERS_WHO_SUPPORT, + user, + metadata, + trackId: `project-deactivated-${ + project.id + }-${user.walletAddress?.toLowerCase()}-${now}`, + }, + })), + ); } async projectListed(params: { project: Project }): Promise { - return; - // const { project } = params; - // const projectOwner = project?.adminUser as User; - // const now = Date.now(); - - // const supporters = await findUsersWhoSupportProject(project.id); - // await sendProjectRelatedNotificationsQueue.addBulk( - // supporters.map(user => ({ - // data: { - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_LISTED_SUPPORTED, - // user, - // trackId: `project-listed-${ - // project.id - // }-${user.walletAddress?.toLowerCase()}-${now}`, - // }, - // })), - // ); - - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_LISTED, - - // sendEmail: true, - // segment: { - // analyticsUserId: projectOwner.segmentUserId(), - // anonymousId: projectOwner.segmentUserId(), - // payload: getSegmentProjectAttributes({ - // project, - // }), - // }, - // trackId: `project-listed-${ - // project.id - // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - // }); + const { project } = params; + const projectOwner = project?.adminUser as User; + const now = Date.now(); + + const supporters = await findUsersWhoSupportProject(project.id); + await sendProjectRelatedNotificationsQueue.addBulk( + supporters.map(user => ({ + data: { + project, + eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_LISTED_SUPPORTED, + user, + trackId: `project-listed-${ + project.id + }-${user.walletAddress?.toLowerCase()}-${now}`, + }, + })), + ); + + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_LISTED, + + sendEmail: true, + segment: { + analyticsUserId: projectOwner.segmentUserId(), + anonymousId: projectOwner.segmentUserId(), + payload: getSegmentProjectAttributes({ + project, + }), + }, + trackId: `project-listed-${ + project.id + }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + }); } async projectEdited(params: { project: Project }): Promise { - return; - // const { project } = params; - // const projectOwner = project?.adminUser as User; - // const now = Date.now(); - - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_EDITED, - - // sendEmail: true, - // segment: { - // analyticsUserId: projectOwner.segmentUserId(), - // anonymousId: projectOwner.segmentUserId(), - // payload: getSegmentProjectAttributes({ - // project, - // }), - // }, - // trackId: `project-edited-${ - // project.id - // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - // }); + const { project } = params; + const projectOwner = project?.adminUser as User; + const now = Date.now(); + + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_EDITED, + + sendEmail: true, + segment: { + analyticsUserId: projectOwner.segmentUserId(), + anonymousId: projectOwner.segmentUserId(), + payload: getSegmentProjectAttributes({ + project, + }), + }, + trackId: `project-edited-${ + project.id + }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + }); } async projectGotDraftByAdmin(params: { project: Project }): Promise { - return; - // const { project } = params; - // const projectOwner = project?.adminUser as User; - // const now = Date.now(); - - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.VERIFICATION_FORM_GOT_DRAFT_BY_ADMIN, - // sendEmail: true, - // segment: { - // analyticsUserId: projectOwner.segmentUserId(), - // anonymousId: projectOwner.segmentUserId(), - // payload: getSegmentProjectAttributes({ - // project, - // }), - // }, - // trackId: `project-got-draft-by-admin-${ - // project.id - // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - // }); + const { project } = params; + const projectOwner = project?.adminUser as User; + const now = Date.now(); + + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.VERIFICATION_FORM_GOT_DRAFT_BY_ADMIN, + sendEmail: true, + segment: { + analyticsUserId: projectOwner.segmentUserId(), + anonymousId: projectOwner.segmentUserId(), + payload: getSegmentProjectAttributes({ + project, + }), + }, + trackId: `project-got-draft-by-admin-${ + project.id + }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + }); } async projectPublished(params: { project: Project }): Promise { - return; - // const { project } = params; - // const projectOwner = project?.adminUser as User; - // const now = Date.now(); - - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.DRAFTED_PROJECT_ACTIVATED, - - // sendEmail: true, - // segment: { - // analyticsUserId: projectOwner.segmentUserId(), - // anonymousId: projectOwner.segmentUserId(), - // payload: getSegmentProjectAttributes({ - // project, - // }), - // }, - // trackId: `project-published-${ - // project.id - // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - // }); + const { project } = params; + const projectOwner = project?.adminUser as User; + const now = Date.now(); + + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.DRAFTED_PROJECT_ACTIVATED, + + sendEmail: true, + segment: { + analyticsUserId: projectOwner.segmentUserId(), + anonymousId: projectOwner.segmentUserId(), + payload: getSegmentProjectAttributes({ + project, + }), + }, + trackId: `project-published-${ + project.id + }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + }); } async projectReactivated(params: { project: Project }): Promise { - return; - // const { project } = params; - // const now = Date.now(); - // const supporters = await findUsersWhoSupportProject(project.id); - // await sendProjectRelatedNotificationsQueue.addBulk( - // supporters.map(user => ({ - // data: { - // project, - // eventName: - // NOTIFICATIONS_EVENT_NAMES.PROJECT_ACTIVATED_USERS_WHO_SUPPORT, - // user, - // trackId: `project-reactivated-${ - // project.id - // }-${user.walletAddress.toLowerCase()}-${now}`, - // }, - // })), - // ); + const { project } = params; + const now = Date.now(); + const supporters = await findUsersWhoSupportProject(project.id); + await sendProjectRelatedNotificationsQueue.addBulk( + supporters.map(user => ({ + data: { + project, + eventName: + NOTIFICATIONS_EVENT_NAMES.PROJECT_ACTIVATED_USERS_WHO_SUPPORT, + user, + trackId: `project-reactivated-${ + project.id + }-${user.walletAddress.toLowerCase()}-${now}`, + }, + })), + ); } async projectSavedAsDraft(params: { project: Project }): Promise { - return; - // const { project } = params; - // const projectOwner = project?.adminUser as User; - // const now = Date.now(); - - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_CREATED, - - // sendEmail: true, - // segment: { - // analyticsUserId: projectOwner.segmentUserId(), - // anonymousId: projectOwner.segmentUserId(), - // payload: getSegmentProjectAttributes({ - // project, - // }), - // }, - // trackId: `project-saved-as-draft-${ - // project.id - // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - // }); + const { project } = params; + const projectOwner = project?.adminUser as User; + const now = Date.now(); + + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_CREATED, + + sendEmail: true, + segment: { + analyticsUserId: projectOwner.segmentUserId(), + anonymousId: projectOwner.segmentUserId(), + payload: getSegmentProjectAttributes({ + project, + }), + }, + trackId: `project-saved-as-draft-${ + project.id + }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + }); } async donationGetPriceFailed(params: { project: Project; donationInfo: { txLink: string; reason: string }; }): Promise { - return; - // const { project, donationInfo } = params; - // const { txLink, reason } = donationInfo; - // const projectOwner = project?.adminUser as User; - // const now = Date.now(); - - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName: NOTIFICATIONS_EVENT_NAMES.DONATION_GET_PRICE_FAILED, - // metadata: { - // txLink, - // reason, - // }, - // sendEmail: false, - // segment: { - // analyticsUserId: projectOwner.segmentUserId(), - // anonymousId: projectOwner.segmentUserId(), - // payload: getSegmentProjectAttributes({ - // project, - // }), - // }, - // trackId: `donation-get-price-failed-${project.id}-${donationInfo.txLink}-${now}`, - // }); + const { project, donationInfo } = params; + const { txLink, reason } = donationInfo; + const projectOwner = project?.adminUser as User; + const now = Date.now(); + + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName: NOTIFICATIONS_EVENT_NAMES.DONATION_GET_PRICE_FAILED, + metadata: { + txLink, + reason, + }, + sendEmail: false, + segment: { + analyticsUserId: projectOwner.segmentUserId(), + anonymousId: projectOwner.segmentUserId(), + payload: getSegmentProjectAttributes({ + project, + }), + }, + trackId: `donation-get-price-failed-${project.id}-${donationInfo.txLink}-${now}`, + }); } async broadcastNotification( params: BroadCastNotificationInputParams, ): Promise { - return; - // const { html, broadCastNotificationId } = params; - // let allUserFetched = false; - // const take = 100; - // let skip = 0; - // const trackIdPrefix = `broadCast-${broadCastNotificationId}`; - // while (!allUserFetched) { - // const { users } = await findAllUsers({ take, skip }); - // if (users.length === 0) { - // allUserFetched = true; - // break; - // } - // skip += users.length; - // const queueData: SendBatchNotificationBody = { notifications: [] }; - // for (const user of users) { - // // with adding .toLowerCase() to wallet address we make sure if two wallet address with different case - // // exist we would set same trackId for them - // const trackId = `${trackIdPrefix}-${user.walletAddress?.toLowerCase()}`; - // if ( - // queueData.notifications.find( - // notificationData => notificationData.trackId === trackId, - // ) - // ) { - // // We should not have items with repetitive trackIds in sending bulk notifications - // // and we may have some users with same wallet address, so we need to add this checking - // // https://github.com/Giveth/giveth-dapps-v2/issues/2084 - // continue; - // } - // queueData.notifications.push({ - // email: user.email as string, - // eventName: NOTIFICATIONS_EVENT_NAMES.RAW_HTML_BROADCAST, - // sendEmail: false, - // sendSegment: false, - // metadata: { - // html, - // }, - // userWalletAddress: user.walletAddress as string, - // trackId, - // }); - // } - // sendBroadcastNotificationsQueue.add(queueData); - // } + const { html, broadCastNotificationId } = params; + let allUserFetched = false; + const take = 100; + let skip = 0; + const trackIdPrefix = `broadCast-${broadCastNotificationId}`; + while (!allUserFetched) { + const { users } = await findAllUsers({ take, skip }); + if (users.length === 0) { + allUserFetched = true; + break; + } + skip += users.length; + const queueData: SendBatchNotificationBody = { notifications: [] }; + for (const user of users) { + // with adding .toLowerCase() to wallet address we make sure if two wallet address with different case + // exist we would set same trackId for them + const trackId = `${trackIdPrefix}-${user.walletAddress?.toLowerCase()}`; + if ( + queueData.notifications.find( + notificationData => notificationData.trackId === trackId, + ) + ) { + // We should not have items with repetitive trackIds in sending bulk notifications + // and we may have some users with same wallet address, so we need to add this checking + // https://github.com/Giveth/giveth-dapps-v2/issues/2084 + continue; + } + queueData.notifications.push({ + email: user.email as string, + eventName: NOTIFICATIONS_EVENT_NAMES.RAW_HTML_BROADCAST, + sendEmail: false, + sendSegment: false, + metadata: { + html, + }, + userWalletAddress: user.walletAddress as string, + trackId, + }); + } + sendBroadcastNotificationsQueue.add(queueData); + } } async projectsHaveNewRank(params: ProjectsHaveNewRankingInputParam) { - return; - // for (const param of params.projectRanks) { - // const project = await findProjectById(param.projectId); - // if (!project) { - // continue; - // } - // const projectOwner = project.adminUser; - // let eventName; - - // // https://github.com/Giveth/impact-graph/issues/774#issuecomment-1542337083 - // if ( - // param.oldRank === params.oldBottomRank && - // param.newRank === params.newBottomRank - // ) { - // // We dont send any notification in this case, because project has no givPower so rank change doesnt matter - // continue; - // } else if (param.oldRank === params.oldBottomRank) { - // eventName = NOTIFICATIONS_EVENT_NAMES.YOUR_PROJECT_GOT_A_RANK; - // } else if (param.newRank < param.oldRank) { - // eventName = NOTIFICATIONS_EVENT_NAMES.PROJECT_HAS_RISEN_IN_THE_RANK; - // } else if (param.newRank > param.oldRank) { - // eventName = NOTIFICATIONS_EVENT_NAMES.PROJECT_HAS_A_NEW_RANK; - // } - // logger.info('send rank changed notification ', { - // eventName, - // slug: project.slug, - // newRank: param.newRank, - // oldRank: param.oldRank, - // oldBottomRank: params.oldBottomRank, - // newBottomRank: params.newBottomRank, - // }); - // await sendProjectRelatedNotificationsQueue.add({ - // project, - // eventName, - // sendEmail: true, - // segment: { - // analyticsUserId: projectOwner.segmentUserId(), - // anonymousId: projectOwner.segmentUserId(), - // payload: getSegmentProjectAttributes({ - // project, - // }), - // }, - // trackId: `project-has-new-rank-${param.round}-${param.projectId}`, - // }); - // } + for (const param of params.projectRanks) { + const project = await findProjectById(param.projectId); + if (!project) { + continue; + } + const projectOwner = project.adminUser; + let eventName; + + // https://github.com/Giveth/impact-graph/issues/774#issuecomment-1542337083 + if ( + param.oldRank === params.oldBottomRank && + param.newRank === params.newBottomRank + ) { + // We dont send any notification in this case, because project has no givPower so rank change doesnt matter + continue; + } else if (param.oldRank === params.oldBottomRank) { + eventName = NOTIFICATIONS_EVENT_NAMES.YOUR_PROJECT_GOT_A_RANK; + } else if (param.newRank < param.oldRank) { + eventName = NOTIFICATIONS_EVENT_NAMES.PROJECT_HAS_RISEN_IN_THE_RANK; + } else if (param.newRank > param.oldRank) { + eventName = NOTIFICATIONS_EVENT_NAMES.PROJECT_HAS_A_NEW_RANK; + } + logger.info('send rank changed notification ', { + eventName, + slug: project.slug, + newRank: param.newRank, + oldRank: param.oldRank, + oldBottomRank: params.oldBottomRank, + newBottomRank: params.newBottomRank, + }); + await sendProjectRelatedNotificationsQueue.add({ + project, + eventName, + sendEmail: true, + segment: { + analyticsUserId: projectOwner.segmentUserId(), + anonymousId: projectOwner.segmentUserId(), + payload: getSegmentProjectAttributes({ + project, + }), + }, + trackId: `project-has-new-rank-${param.round}-${param.projectId}`, + }); + } } } From 85d5869b5c1d0065b265aa28aec39ff1501465b6 Mon Sep 17 00:00:00 2001 From: Carlos Date: Tue, 16 Jan 2024 11:42:52 -0500 Subject: [PATCH 20/34] set logic as before, comment notification center methods not required --- .../NotificationCenterAdapter.ts | 170 +++++++++--------- src/resolvers/projectResolver.ts | 14 +- src/resolvers/reactionResolver.ts | 5 + src/services/donationService.ts | 8 + 4 files changed, 113 insertions(+), 84 deletions(-) diff --git a/src/adapters/notifications/NotificationCenterAdapter.ts b/src/adapters/notifications/NotificationCenterAdapter.ts index 939bdc5c9..7a031f420 100644 --- a/src/adapters/notifications/NotificationCenterAdapter.ts +++ b/src/adapters/notifications/NotificationCenterAdapter.ts @@ -114,36 +114,37 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface { project: Project; donor: User; }): Promise { - const { project, donor, donation } = params; - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.MADE_DONATION, - user: { - email: donor.email, - walletAddress: donor.walletAddress as string, - }, - sendEmail: true, - segment: { - analyticsUserId: donor.segmentUserId(), - anonymousId: donor.segmentUserId(), - payload: { - ...getSegmentDonationAttributes({ - donation, - project, - user: donor, - }), - - // We just want this to be donation sent event not made donation, so don put it in getSegmentDonationAttributes() - // see https://github.com/Giveth/impact-graph/pull/716 - fromWalletAddress: donation.fromWalletAddress.toLowerCase(), - }, - }, - trackId: - 'donation-sent-' + - donation.transactionNetworkId + - '-' + - donation.transactionId, - }); + return; + // const { project, donor, donation } = params; + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: projectEdited.MADE_DONATION, + // user: { + // email: donor.email, + // walletAddress: donor.walletAddress as string, + // }, + // sendEmail: true, + // segment: { + // analyticsUserId: donor.segmentUserId(), + // anonymousId: donor.segmentUserId(), + // payload: { + // ...getSegmentDonationAttributes({ + // donation, + // project, + // user: donor, + // }), + + // // We just want this to be donation sent event not made donation, so don put it in getSegmentDonationAttributes() + // // see https://github.com/Giveth/impact-graph/pull/716 + // fromWalletAddress: donation.fromWalletAddress.toLowerCase(), + // }, + // }, + // trackId: + // 'donation-sent-' + + // donation.transactionNetworkId + + // '-' + + // donation.transactionId, + // }); } async projectVerified(params: { project: Project }): Promise { @@ -406,19 +407,20 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface { project: Project; userId: number; }): Promise { - const { project } = params; - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_RECEIVED_HEART, - - // With adding trackId to notification, notification-center would not create new notification - // If there is already a notification with this trackId in DB - trackId: generateTrackId({ - userId: params.userId, - projectId: project?.id as number, - action: 'likeProject', - }), - }); + return; + // const { project } = params; + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_RECEIVED_HEART, + + // // With adding trackId to notification, notification-center would not create new notification + // // If there is already a notification with this trackId in DB + // trackId: generateTrackId({ + // userId: params.userId, + // projectId: project?.id as number, + // action: 'likeProject', + // }), + // }); } ProfileIsCompleted(params: { user: User }): Promise { @@ -630,26 +632,27 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface { } async projectEdited(params: { project: Project }): Promise { - const { project } = params; - const projectOwner = project?.adminUser as User; - const now = Date.now(); - - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_EDITED, - - sendEmail: true, - segment: { - analyticsUserId: projectOwner.segmentUserId(), - anonymousId: projectOwner.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `project-edited-${ - project.id - }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - }); + return; + // const { project } = params; + // const projectOwner = project?.adminUser as User; + // const now = Date.now(); + + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.PROJECT_EDITED, + + // sendEmail: true, + // segment: { + // analyticsUserId: projectOwner.segmentUserId(), + // anonymousId: projectOwner.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `project-edited-${ + // project.id + // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + // }); } async projectGotDraftByAdmin(params: { project: Project }): Promise { const { project } = params; @@ -674,26 +677,27 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface { } async projectPublished(params: { project: Project }): Promise { - const { project } = params; - const projectOwner = project?.adminUser as User; - const now = Date.now(); - - await sendProjectRelatedNotificationsQueue.add({ - project, - eventName: NOTIFICATIONS_EVENT_NAMES.DRAFTED_PROJECT_ACTIVATED, - - sendEmail: true, - segment: { - analyticsUserId: projectOwner.segmentUserId(), - anonymousId: projectOwner.segmentUserId(), - payload: getSegmentProjectAttributes({ - project, - }), - }, - trackId: `project-published-${ - project.id - }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, - }); + return; + // const { project } = params; + // const projectOwner = project?.adminUser as User; + // const now = Date.now(); + + // await sendProjectRelatedNotificationsQueue.add({ + // project, + // eventName: NOTIFICATIONS_EVENT_NAMES.DRAFTED_PROJECT_ACTIVATED, + + // sendEmail: true, + // segment: { + // analyticsUserId: projectOwner.segmentUserId(), + // anonymousId: projectOwner.segmentUserId(), + // payload: getSegmentProjectAttributes({ + // project, + // }), + // }, + // trackId: `project-published-${ + // project.id + // }-${projectOwner.walletAddress?.toLowerCase()}-${now}`, + // }); } async projectReactivated(params: { project: Project }): Promise { diff --git a/src/resolvers/projectResolver.ts b/src/resolvers/projectResolver.ts index 02f3f4e08..b21ed5f69 100644 --- a/src/resolvers/projectResolver.ts +++ b/src/resolvers/projectResolver.ts @@ -1054,6 +1054,10 @@ export class ProjectResolver { project.addresses = await findProjectRecipientAddressByProjectId({ projectId, }); + + // Edit emails + await getNotificationAdapter().projectEdited({ project }); + return project; } @@ -1327,6 +1331,10 @@ export class ProjectResolver { await getNotificationAdapter().projectSavedAsDraft({ project, }); + } else { + await getNotificationAdapter().projectPublished({ + project, + }); } return project; @@ -1969,7 +1977,11 @@ export class ProjectResolver { await project.save(); - if (project.prevStatusId !== ProjStatus.drafted) { + if (project.prevStatusId === ProjStatus.drafted) { + await getNotificationAdapter().projectPublished({ + project, + }); + } else { await getNotificationAdapter().projectReactivated({ project, }); diff --git a/src/resolvers/reactionResolver.ts b/src/resolvers/reactionResolver.ts index 5cc0df298..b38b89d66 100644 --- a/src/resolvers/reactionResolver.ts +++ b/src/resolvers/reactionResolver.ts @@ -213,6 +213,11 @@ export class ReactionResolver { // commit transaction now: await queryRunner.commitTransaction(); + await getNotificationAdapter().projectReceivedHeartReaction({ + project, + userId: user.userId, + }); + return reaction; } catch (e) { logger.error('like project error', e); diff --git a/src/services/donationService.ts b/src/services/donationService.ts index 922f40345..a6d34e428 100644 --- a/src/services/donationService.ts +++ b/src/services/donationService.ts @@ -468,6 +468,14 @@ export const sendSegmentEventForDonation = async (params: { project, }); } + + if (donorUser) { + await getNotificationAdapter().donationSent({ + donation, + project, + donor: donorUser, + }); + } }; export const insertDonationsFromQfRoundHistory = async (): Promise => { From 673bc8ce56fe0ba5c2116235162ecc7abb8924ff Mon Sep 17 00:00:00 2001 From: Carlos Date: Tue, 16 Jan 2024 12:02:03 -0500 Subject: [PATCH 21/34] move donation received logic to notification adapter --- src/adapters/notifications/NotificationCenterAdapter.ts | 3 +++ src/services/donationService.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/adapters/notifications/NotificationCenterAdapter.ts b/src/adapters/notifications/NotificationCenterAdapter.ts index 7a031f420..aeccaed47 100644 --- a/src/adapters/notifications/NotificationCenterAdapter.ts +++ b/src/adapters/notifications/NotificationCenterAdapter.ts @@ -86,6 +86,8 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface { donation: Donation; project: Project; }): Promise { + if (params.donation.valueUsd <= 1) return; + const { project, donation } = params; const user = project.adminUser as User; await sendProjectRelatedNotificationsQueue.add({ @@ -631,6 +633,7 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface { }); } + // commenting for now to test load of notification center. async projectEdited(params: { project: Project }): Promise { return; // const { project } = params; diff --git a/src/services/donationService.ts b/src/services/donationService.ts index a6d34e428..f61b39dc0 100644 --- a/src/services/donationService.ts +++ b/src/services/donationService.ts @@ -462,7 +462,7 @@ export const sendSegmentEventForDonation = async (params: { } const donorUser = await findUserById(donation.userId); const projectOwner = project.adminUser; - if (projectOwner && donation.valueUsd > 1) { + if (projectOwner) { await getNotificationAdapter().donationReceived({ donation, project, From 43211872e056bc4fba912cf3d6242f2512e50b55 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Wed, 17 Jan 2024 11:19:45 +0330 Subject: [PATCH 22/34] Refactor get networkId for solana addresses and implement getAppropriateNetworkId --- migration/data/seedTokens.ts | 2 +- src/repositories/projectRepository.ts | 14 +++++++------ .../projectVerificationRepository.ts | 13 +++++++----- src/resolvers/donationResolver.ts | 13 +++++++----- src/resolvers/projectResolver.ts | 21 +++++++++++-------- src/services/chains/index.ts | 9 ++++++++ 6 files changed, 46 insertions(+), 26 deletions(-) diff --git a/migration/data/seedTokens.ts b/migration/data/seedTokens.ts index fff0b8bda..7d38c8397 100644 --- a/migration/data/seedTokens.ts +++ b/migration/data/seedTokens.ts @@ -1372,7 +1372,7 @@ const seedTokens: ITokenData[] = [ symbol: 'SOL', address: SOLANA_SYSTEM_PROGRAM, decimals: 9, - networkId: NETWORK_IDS.SOLANA_DEVNET, + networkId: NETWORK_IDS.SOLANA_TESTNET, chainType: ChainType.SOLANA, coingeckoId: COINGECKO_TOKEN_IDS.SOLANA, }, diff --git a/src/repositories/projectRepository.ts b/src/repositories/projectRepository.ts index 6ae890999..8f60bfca2 100644 --- a/src/repositories/projectRepository.ts +++ b/src/repositories/projectRepository.ts @@ -14,7 +14,10 @@ import { User, publicSelectionFields } from '../entities/user'; import { ResourcesTotalPerMonthAndYear } from '../resolvers/donationResolver'; import { OrderDirection, ProjectResolver } from '../resolvers/projectResolver'; import { ChainType } from '../types/network'; -import { getDefaultSolanaChainId } from '../services/chains'; +import { + getAppropriateNetworkId, + getDefaultSolanaChainId, +} from '../services/chains'; export const findProjectById = (projectId: number): Promise => { // return Project.findOne({ id: projectId }); @@ -300,11 +303,10 @@ export const updateProjectWithVerificationForm = async ( address: relatedAddress.address, // Frontend doesn't send networkId for solana addresses so we set it to default solana chain id - networkId: - relatedAddress.chainType === ChainType.SOLANA - ? getDefaultSolanaChainId() - : relatedAddress.networkId, - + networkId: getAppropriateNetworkId({ + networkId: relatedAddress.networkId, + chainType: relatedAddress.chainType, + }), projectId: verificationForm.projectId, userId: verificationForm.user?.id, project, diff --git a/src/repositories/projectVerificationRepository.ts b/src/repositories/projectVerificationRepository.ts index f55ef4c35..31632256f 100644 --- a/src/repositories/projectVerificationRepository.ts +++ b/src/repositories/projectVerificationRepository.ts @@ -17,7 +17,10 @@ import { translationErrorMessagesKeys, } from '../utils/errorMessages'; import { User } from '../entities/user'; -import { getDefaultSolanaChainId } from '../services/chains'; +import { + getAppropriateNetworkId, + getDefaultSolanaChainId, +} from '../services/chains'; import { ChainType } from '../types/network'; export const createProjectVerificationForm = async (params: { @@ -310,10 +313,10 @@ export const updateManagingFundsOfProjectVerification = async (params: { managingFunds.relatedAddresses = managingFunds.relatedAddresses.map( address => { // because frontend sends 0 for solana addresses - address.networkId = - address.chainType === ChainType.SOLANA - ? getDefaultSolanaChainId() - : address.networkId; + address.networkId = getAppropriateNetworkId({ + networkId: address.networkId, + chainType: address.chainType, + }); return address; }, diff --git a/src/resolvers/donationResolver.ts b/src/resolvers/donationResolver.ts index b5c7c0b44..b9a236543 100644 --- a/src/resolvers/donationResolver.ts +++ b/src/resolvers/donationResolver.ts @@ -60,7 +60,10 @@ import { getChainvineReferralInfoForDonation } from '../services/chainvineReferr import { relatedActiveQfRoundForProject } from '../services/qfRoundService'; import { detectAddressChainType } from '../utils/networks'; import { ChainType } from '../types/network'; -import { getDefaultSolanaChainId } from '../services/chains'; +import { + getAppropriateNetworkId, + getDefaultSolanaChainId, +} from '../services/chains'; @ObjectType() class PaginateDonations { @@ -626,10 +629,10 @@ export class DonationResolver { throw new Error(i18n.__(translationErrorMessagesKeys.UN_AUTHORIZED)); } const chainType = detectAddressChainType(donorUser.walletAddress!); - const networkId = - chainType === ChainType.SOLANA - ? getDefaultSolanaChainId() - : transactionNetworkId; + const networkId = getAppropriateNetworkId({ + networkId: transactionNetworkId, + chainType, + }); try { validateWithJoiSchema( diff --git a/src/resolvers/projectResolver.ts b/src/resolvers/projectResolver.ts index 7906faa7a..bf22ce349 100644 --- a/src/resolvers/projectResolver.ts +++ b/src/resolvers/projectResolver.ts @@ -107,7 +107,10 @@ import { ProjectBySlugResponse } from './types/projectResolver'; import { ChainType } from '../types/network'; import { findActiveQfRound } from '../repositories/qfRoundRepository'; import { getAllProjectsRelatedToActiveCampaigns } from '../services/campaignService'; -import { getDefaultSolanaChainId } from '../services/chains'; +import { + getAppropriateNetworkId, + getDefaultSolanaChainId, +} from '../services/chains'; const projectFiltersCacheDuration = Number( process.env.PROJECT_FILTERS_THREADS_POOL_DURATION || 60000, @@ -1044,10 +1047,10 @@ export class ProjectResolver { chainType: relatedAddress.chainType, // Frontend doesn't send networkId for solana addresses so we set it to default solana chain id - networkId: - relatedAddress.chainType === ChainType.SOLANA - ? getDefaultSolanaChainId() - : relatedAddress.networkId, + networkId: getAppropriateNetworkId({ + networkId: relatedAddress.networkId, + chainType: relatedAddress.chainType, + }), isRecipient: true, }; @@ -1313,10 +1316,10 @@ export class ProjectResolver { address: chainType === ChainType.EVM ? address.toLowerCase() : address, chainType, - networkId: - chainType === ChainType.SOLANA - ? getDefaultSolanaChainId() - : networkId, + networkId: getAppropriateNetworkId({ + networkId, + chainType, + }), isRecipient: true, }; }), diff --git a/src/services/chains/index.ts b/src/services/chains/index.ts index 41426df24..db635f13a 100644 --- a/src/services/chains/index.ts +++ b/src/services/chains/index.ts @@ -90,3 +90,12 @@ export async function getTransactionInfoFromNetwork( export function getDefaultSolanaChainId(): number { return Number(process.env.SOLANA_CHAIN_ID) || NETWORK_IDS.SOLANA_DEVNET; } + +export function getAppropriateNetworkId(params: { + chainType?: ChainType; + networkId: number; +}): number { + return params.chainType === ChainType.SOLANA + ? getDefaultSolanaChainId() + : params.networkId; +} From 86084a7205748136b855b638adc1fa5369aaf401 Mon Sep 17 00:00:00 2001 From: mohammadranjbarz Date: Thu, 18 Jan 2024 14:36:19 +0330 Subject: [PATCH 23/34] Fix filling solana donations price (#1252) * Fix filling solana donations price related to https://github.com/Giveth/giveth-dapps-v2/issues/3394#issuecomment-1896557906 * Add informative logs for filling value usd part * Add log for createDonation webservice * change type of transactionNetworkId to number in ajv schema * Update createDonation to use correct networkId --- src/adapters/price/MonoswapPriceAdapter.ts | 48 ++++++++++-------- src/resolvers/donationResolver.ts | 49 +++++++++---------- src/services/donationService.test.ts | 3 -- src/services/donationService.ts | 27 +++++----- .../validators/graphqlQueryValidators.ts | 2 +- 5 files changed, 66 insertions(+), 63 deletions(-) diff --git a/src/adapters/price/MonoswapPriceAdapter.ts b/src/adapters/price/MonoswapPriceAdapter.ts index 649f781ac..d0537b3cb 100644 --- a/src/adapters/price/MonoswapPriceAdapter.ts +++ b/src/adapters/price/MonoswapPriceAdapter.ts @@ -4,30 +4,36 @@ import { } from './PriceAdapterInterface'; import { CHAIN_ID } from '@giveth/monoswap/dist/src/sdk/sdkFactory'; import { getMonoSwapTokenPrices } from '../../services/donationService'; +import { logger } from '../../utils/logger'; export class MonoswapPriceAdapter implements PriceAdapterInterface { async getTokenPrice(params: GetTokenPriceParams): Promise { - let baseTokens: string[]; - switch (params.networkId) { - case CHAIN_ID.XDAI: - baseTokens = ['WXDAI', 'WETH']; - break; - case CHAIN_ID.POLYGON: - baseTokens = ['USDC', 'MATIC']; - break; - case CHAIN_ID.CELO: - case CHAIN_ID.ALFAJORES: - baseTokens = ['cUSD', 'CELO']; - break; - default: - baseTokens = ['USDT', 'ETH']; - break; + try { + let baseTokens: string[]; + switch (params.networkId) { + case CHAIN_ID.XDAI: + baseTokens = ['WXDAI', 'WETH']; + break; + case CHAIN_ID.POLYGON: + baseTokens = ['USDC', 'MATIC']; + break; + case CHAIN_ID.CELO: + case CHAIN_ID.ALFAJORES: + baseTokens = ['cUSD', 'CELO']; + break; + default: + baseTokens = ['USDT', 'ETH']; + break; + } + const tokenPrices = await getMonoSwapTokenPrices( + params.symbol, + baseTokens, + params.networkId, + ); + return Number(tokenPrices[0]); + } catch (e) { + logger.error('Error in MonoswapPriceAdapter', e); + throw e; } - const tokenPrices = await getMonoSwapTokenPrices( - params.symbol, - baseTokens, - params.networkId, - ); - return Number(tokenPrices[0]); } } diff --git a/src/resolvers/donationResolver.ts b/src/resolvers/donationResolver.ts index b9a236543..d96979673 100644 --- a/src/resolvers/donationResolver.ts +++ b/src/resolvers/donationResolver.ts @@ -634,25 +634,27 @@ export class DonationResolver { chainType, }); + const validaDataInput = { + amount, + transactionId, + transactionNetworkId: networkId, + anonymous, + tokenAddress, + token, + projectId, + nonce, + transakId, + referrerId, + safeTransactionId, + chainType, + }; try { - validateWithJoiSchema( - { - amount, - transactionId, - transactionNetworkId: networkId, - anonymous, - tokenAddress, - token, - projectId, - nonce, - transakId, - referrerId, - safeTransactionId, - chainType, - }, - createDonationQueryValidator, - ); + validateWithJoiSchema(validaDataInput, createDonationQueryValidator); } catch (e) { + logger.error( + 'Error on validating createDonation input', + validaDataInput, + ); // Joi alternatives does not handle custom errors, have to catch them. if (e.message.includes('does not match any of the allowed types')) { throw new Error( @@ -678,7 +680,7 @@ export class DonationResolver { } const tokenInDb = await Token.findOne({ where: { - networkId: transactionNetworkId, + networkId, symbol: token, }, }); @@ -771,17 +773,13 @@ export class DonationResolver { ); if ( activeQfRoundForProject && - activeQfRoundForProject.isEligibleNetwork(Number(transactionNetworkId)) + activeQfRoundForProject.isEligibleNetwork(networkId) ) { donation.qfRound = activeQfRoundForProject; } await donation.save(); - let priceChainId = - transactionNetworkId === NETWORK_IDS.ROPSTEN || - transactionNetworkId === NETWORK_IDS.GOERLI - ? NETWORK_IDS.MAIN_NET - : transactionNetworkId; + let priceChainId; switch (transactionNetworkId) { case NETWORK_IDS.ROPSTEN: @@ -797,7 +795,7 @@ export class DonationResolver { priceChainId = NETWORK_IDS.ETC; break; default: - priceChainId = transactionNetworkId; + priceChainId = networkId; break; } @@ -808,7 +806,6 @@ export class DonationResolver { token, priceChainId, amount, - chainType!, ); return donation.id; diff --git a/src/services/donationService.test.ts b/src/services/donationService.test.ts index c2ccad323..5f6c7b023 100644 --- a/src/services/donationService.test.ts +++ b/src/services/donationService.test.ts @@ -781,7 +781,6 @@ function fillOldStableCoinDonationsPriceTestCases() { token, CHAIN_ID.POLYGON, amount, - ChainType.EVM, ); donation = (await findDonationById(donation.id))!; @@ -814,7 +813,6 @@ function fillOldStableCoinDonationsPriceTestCases() { token, CHAIN_ID.CELO, amount, - ChainType.EVM, ); donation = (await findDonationById(donation.id))!; expect(donation.valueUsd).to.gt(0); @@ -846,7 +844,6 @@ function fillOldStableCoinDonationsPriceTestCases() { token, CHAIN_ID.ALFAJORES, amount, - ChainType.EVM, ); donation = (await findDonationById(donation.id))!; diff --git a/src/services/donationService.ts b/src/services/donationService.ts index f61b39dc0..3e9573676 100644 --- a/src/services/donationService.ts +++ b/src/services/donationService.ts @@ -54,18 +54,15 @@ export const updateDonationPricesAndValues = async ( currency: string, priceChainId: number, amount: string | number, - chainType: string = ChainType.EVM, ) => { + logger.info('updateDonationPricesAndValues() has been called', { + donationId: donation.id, + projectId: project.id, + token: token?.symbol, + priceChainId, + }); try { - if (chainType === ChainType.SOLANA && token) { - const coingeckoAdapter = new CoingeckoPriceAdapter(); - const solanaPriceUsd = await coingeckoAdapter.getTokenPrice({ - symbol: token.coingeckoId, - networkId: NETWORK_IDS.SOLANA_MAINNET, - }); - donation.priceUsd = toFixNumber(solanaPriceUsd, 4); - donation.valueUsd = toFixNumber(donation.amount * solanaPriceUsd, 4); - } else if (token?.isStableCoin) { + if (token?.isStableCoin) { donation.priceUsd = 1; donation.valueUsd = Number(amount); } else if (currency === 'GIV') { @@ -91,14 +88,13 @@ export const updateDonationPricesAndValues = async ( symbol: currency, networkId: priceChainId, }); - if (priceUsd) { donation.priceUsd = Number(priceUsd); donation.valueUsd = toFixNumber(Number(amount) * donation.priceUsd, 4); } } } catch (e) { - logger.error('Error in getting price from monoswap', { + logger.error('Error in getting price from donation', { error: e, donation, }); @@ -124,6 +120,13 @@ export const updateDonationPricesAndValues = async ( }, ); } + logger.info('updateDonationPricesAndValues() result', { + valueUsd: donation.valueUsd, + donationId: donation.id, + projectId: project.id, + token: token?.symbol, + priceChainId, + }); const { givbackFactor, projectRank, bottomRankInRound, powerRound } = await calculateGivbackFactor(project.id); donation.givbackFactor = givbackFactor; diff --git a/src/utils/validators/graphqlQueryValidators.ts b/src/utils/validators/graphqlQueryValidators.ts index 9b9a2569a..750a0cb87 100644 --- a/src/utils/validators/graphqlQueryValidators.ts +++ b/src/utils/validators/graphqlQueryValidators.ts @@ -95,7 +95,7 @@ export const createDonationQueryValidator = Joi.object({ ), }), }), - transactionNetworkId: Joi.string() + transactionNetworkId: Joi.number() .required() .valid(...Object.values(NETWORK_IDS)), From 494766ff2d1b24ac6f478c97ba50a247c642b4ca Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Mon, 22 Jan 2024 17:39:20 +0330 Subject: [PATCH 24/34] Added jobId to donation verification queue --- src/services/cronJobs/syncDonationsWithNetwork.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/services/cronJobs/syncDonationsWithNetwork.ts b/src/services/cronJobs/syncDonationsWithNetwork.ts index 6fdd9c75e..1d85420a1 100644 --- a/src/services/cronJobs/syncDonationsWithNetwork.ts +++ b/src/services/cronJobs/syncDonationsWithNetwork.ts @@ -49,9 +49,14 @@ const addJobToCheckPendingDonationsWithNetwork = async () => { logger.debug('Pending donations to be check', donations.length); donations.forEach(donation => { logger.debug('Add pending donation to queue', { donationId: donation.id }); - verifyDonationsQueue.add({ - donationId: donation.id, - }); + verifyDonationsQueue.add( + { + donationId: donation.id, + }, + { + jobId: `verify-donation-id-${donation.id}`, + }, + ); }); }; From 693ef0e561e061c0982205f3a818fd2476ee9a1d Mon Sep 17 00:00:00 2001 From: Carlos Date: Mon, 22 Jan 2024 19:04:52 -0500 Subject: [PATCH 25/34] fix test env --- config/test.env | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/config/test.env b/config/test.env index de136a683..f7df0aa72 100644 --- a/config/test.env +++ b/config/test.env @@ -195,11 +195,8 @@ MATCHING_FUND_DONATIONS_FROM_ADDRESS=0x6e8873085530406995170Da467010565968C7C62 # Rate Limit config DISABLE_SERVER_RATE_LIMITER=false DISABLE_SERVER_CORS=false -<<<<<<< HEAD -======= -SOLANA_NODE_RPC_URL= ->>>>>>> master +SOLANA_NODE_RPC_URL= SOLANA_MAINNET_NODE_RPC_URL= SOLANA_DEVNET_NODE_RPC_URL= SOLANA_TEST_NODE_RPC_URL= @@ -212,4 +209,3 @@ LOST_DONATIONS_QF_ROUND= LOST_DONATIONS_NETWORK_ID=10 DISABLE_NOTIFICATION_CENTER=false ->>>>>>> master From de14c4b1755326546b9660f1c65da954a3334832 Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Tue, 23 Jan 2024 11:54:35 +0330 Subject: [PATCH 26/34] Import from Donation Backup Service (#1253) * Added Donation Save Backup Adapter * add backup service import cronjob * add interface of used params from mongo data * Change loading urls for backup service * Refactor donationSaveBackupAdapter and adding mock adapter * Removed unused package script * Refactored createBackupDonation to reuse resolver * Fix createBackupDonation() and write test case for that * Add importError to failed donation mongo backup --------- Co-authored-by: mohammadranjbarz Co-authored-by: Carlos --- config/example.env | 13 ++ config/test.env | 4 + package.json | 1 + src/adapters/adaptersFactory.ts | 16 ++ .../DonationSaveBackupInterface.ts | 43 ++++ .../DonationSaveBackupMockAdapter.ts | 41 ++++ .../donationSaveBackupAdapter.ts | 218 ++++++++++++++++++ src/resolvers/donationResolver.ts | 3 +- src/server/bootstrap.ts | 5 + .../cronJobs/backupDonationImport.test.ts | 85 +++++++ .../cronJobs/backupDonationImportJob.ts | 99 ++++++++ src/utils/errorMessages.ts | 2 + src/utils/utils.test.ts | 16 +- src/utils/utils.ts | 4 + .../validators/graphqlQueryValidators.ts | 2 +- 15 files changed, 548 insertions(+), 4 deletions(-) create mode 100644 src/adapters/donationSaveBackup/DonationSaveBackupInterface.ts create mode 100644 src/adapters/donationSaveBackup/DonationSaveBackupMockAdapter.ts create mode 100644 src/adapters/donationSaveBackup/donationSaveBackupAdapter.ts create mode 100644 src/services/cronJobs/backupDonationImport.test.ts create mode 100644 src/services/cronJobs/backupDonationImportJob.ts diff --git a/config/example.env b/config/example.env index 3b0030031..892420053 100644 --- a/config/example.env +++ b/config/example.env @@ -247,3 +247,16 @@ LOST_DONATIONS_NETWORK_ID= SOLANA_CHAIN_ID=103 DISABLE_NOTIFICATION_CENTER= +ENABLE_IMPORT_DONATION_BACKUP=false +DONATION_SAVE_BACKUP_API_URL= +DONATION_SAVE_BACKUP_API_SECRET= +DONATION_SAVE_BACKUP_CRONJOB_EXPRESSION= +DONATION_SAVE_BACKUP_DATA_SOURCE= + +# default value is failed_donation +DONATION_SAVE_BACKUP_COLLECTION= +# default value is failed_donation +DONATION_SAVE_BACKUP_DATABASE= + +# Default value is saveBackup +DONATION_SAVE_BACKUP_ADAPTER=saveBackup diff --git a/config/test.env b/config/test.env index f7df0aa72..e3bb93c6d 100644 --- a/config/test.env +++ b/config/test.env @@ -209,3 +209,7 @@ LOST_DONATIONS_QF_ROUND= LOST_DONATIONS_NETWORK_ID=10 DISABLE_NOTIFICATION_CENTER=false +DONATION_SAVE_BACKUP_CRONJOB_EXPRESSION= +ENABLE_IMPORT_DONATION_BACKUP=false +DONATION_SAVE_BACKUP_API_URL= +DONATION_SAVE_BACKUP_API_SECRET= \ No newline at end of file diff --git a/package.json b/package.json index 1227dd911..5547ef687 100644 --- a/package.json +++ b/package.json @@ -114,6 +114,7 @@ "tslint:fix": "tslint -c tslint.json --fix '{src,test,migration}/**/*.ts'", "test": "NODE_ENV=test mocha --config ./.mocharc.all-test.json", "test:syncProjectsRequiredForListing": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/cronJobs/syncProjectsRequiredForListing.test.ts", + "test:backupDonationImport": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/cronJobs/backupDonationImport.test.ts", "test:projectEntity": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/entities/project.test.ts", "test:projectValidators": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/utils/validators/projectValidator.test.ts", "test:onramperWebhook": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/onramper/webhookHandler.test.ts", diff --git a/src/adapters/adaptersFactory.ts b/src/adapters/adaptersFactory.ts index a62af50fd..dbaa1961e 100644 --- a/src/adapters/adaptersFactory.ts +++ b/src/adapters/adaptersFactory.ts @@ -21,6 +21,8 @@ import { GitcoinAdapter } from './gitcoin/gitcoinAdapter'; import { GitcoinMockAdapter } from './gitcoin/gitcoinMockAdapter'; import { GivPowerBalanceAggregatorAdapter } from './givPowerBalanceAggregator/givPowerBalanceAggregatorAdapter'; import { GivPowerBalanceAggregatorAdapterMock } from './givPowerBalanceAggregator/givPowerBalanceAggregatorAdapterMock'; +import { DonationSaveBackupAdapter } from './donationSaveBackup/donationSaveBackupAdapter'; +import { DonationSaveBackupMockAdapter } from './donationSaveBackup/DonationSaveBackupMockAdapter'; const discordAdapter = new DiscordAdapter(); const googleAdapter = new GoogleAdapter(); @@ -118,3 +120,17 @@ export const getPowerBalanceAggregatorAdapter = () => { return mockPowerBalanceAggregator; } }; + +const donationSaveBackupAdapter = new DonationSaveBackupAdapter(); +const mockDonationSaveBackupAdapter = new DonationSaveBackupMockAdapter(); + +export const getDonationSaveBackupAdapter = () => { + switch (process.env.DONATION_SAVE_BACKUP_ADAPTER) { + case 'saveBackup': + return donationSaveBackupAdapter; + case 'mock': + return mockDonationSaveBackupAdapter; + default: + return mockDonationSaveBackupAdapter; + } +}; diff --git a/src/adapters/donationSaveBackup/DonationSaveBackupInterface.ts b/src/adapters/donationSaveBackup/DonationSaveBackupInterface.ts new file mode 100644 index 000000000..d71b45dcc --- /dev/null +++ b/src/adapters/donationSaveBackup/DonationSaveBackupInterface.ts @@ -0,0 +1,43 @@ +export type FetchedSavedFailDonationInterface = { + _id: string; + txHash: string; + imported?: boolean; + importError?: string; + token: { + symbol: string; + address: string; + networkId: number; + }; + walletAddress: string; + amount: number; + chainId: number; + projectId: number; + anonymous: boolean; + nonce: number; + symbol: string; + chainvineReferred?: string; + safeTransactionId?: string; +}; + +export interface DonationSaveBackupInterface { + getNotImportedDonationsFromBackup(params: { + limit: number; + }): Promise; + + getSingleDonationFromBackupByTxHash( + txHash: string, + ): Promise; + + markDonationAsImported(donationMongoId: string): Promise; + + unmarkDonationAsImported(donationMongoId: string): Promise; + + getSingleDonationFromBackupById( + donationMongoId: string, + ): Promise; + + markDonationAsImportError( + donationMongoId: string, + errorMessage, + ): Promise; +} diff --git a/src/adapters/donationSaveBackup/DonationSaveBackupMockAdapter.ts b/src/adapters/donationSaveBackup/DonationSaveBackupMockAdapter.ts new file mode 100644 index 000000000..4de1f8755 --- /dev/null +++ b/src/adapters/donationSaveBackup/DonationSaveBackupMockAdapter.ts @@ -0,0 +1,41 @@ +import { + DonationSaveBackupInterface, + FetchedSavedFailDonationInterface, +} from './DonationSaveBackupInterface'; + +export class DonationSaveBackupMockAdapter + implements DonationSaveBackupInterface +{ + async getNotImportedDonationsFromBackup(params: { + limit: number; + }): Promise { + return []; + } + + async getSingleDonationFromBackupByTxHash( + txHash: string, + ): Promise { + return null; + } + + async markDonationAsImported(donationMongoId: string): Promise { + // + } + + async unmarkDonationAsImported(donationMongoId: string): Promise { + // + } + + async getSingleDonationFromBackupById( + donationMongoId: string, + ): Promise { + return null; + } + + markDonationAsImportError( + donationMongoId: string, + errorMessage, + ): Promise { + return Promise.resolve(undefined); + } +} diff --git a/src/adapters/donationSaveBackup/donationSaveBackupAdapter.ts b/src/adapters/donationSaveBackup/donationSaveBackupAdapter.ts new file mode 100644 index 000000000..7a48e6c3b --- /dev/null +++ b/src/adapters/donationSaveBackup/donationSaveBackupAdapter.ts @@ -0,0 +1,218 @@ +// a method the get objects from mongodb api read from config DONATION_SAVE_BACKUP_API_URL with sercret read from DONATION_SAVE_BACKUP_API_SECRET, +// it must filter objects by those doesn't have `imported` field with true value +// also must support pagination + +import { logger } from '../../utils/logger'; +import config from '../../config'; +import axios from 'axios'; +import { + DonationSaveBackupInterface, + FetchedSavedFailDonationInterface, +} from './DonationSaveBackupInterface'; + +const DONATION_SAVE_BACKUP_API_URL = config.get( + 'DONATION_SAVE_BACKUP_API_URL', +) as string; +const DONATION_SAVE_BACKUP_API_SECRET = config.get( + 'DONATION_SAVE_BACKUP_API_SECRET', +) as string; +const DONATION_SAVE_BACKUP_DATA_SOURCE = config.get( + 'DONATION_SAVE_BACKUP_DATA_SOURCE', +) as string; +const DONATION_SAVE_BACKUP_COLLECTION = + config.get('DONATION_SAVE_BACKUP_COLLECTION') || 'failed_donation'; +const DONATION_SAVE_BACKUP_DATABASE = + config.get('DONATION_SAVE_BACKUP_DATABASE') || 'failed_donation'; + +// add '/' if doesn't exist at the +const baseUrl = DONATION_SAVE_BACKUP_API_URL.endsWith('/') + ? DONATION_SAVE_BACKUP_API_URL + : `${DONATION_SAVE_BACKUP_API_URL}/`; + +export class DonationSaveBackupAdapter implements DonationSaveBackupInterface { + async getNotImportedDonationsFromBackup(params: { + limit: number; + }): Promise { + const result = await axios.post( + `${baseUrl}find`, + { + collection: DONATION_SAVE_BACKUP_COLLECTION, + database: DONATION_SAVE_BACKUP_DATABASE, + dataSource: DONATION_SAVE_BACKUP_DATA_SOURCE, + limit: params.limit, + filter: { + imported: { $ne: true }, + importError: { $ne: true }, + }, + sort: { _id: 1 }, + }, + { + headers: { + 'api-key': DONATION_SAVE_BACKUP_API_SECRET, + 'Content-Type': 'application/json', + 'Access-Control-Request-Headers': '*', + }, + }, + ); + + if (result.status !== 200) { + logger.error('getNotImportedDonationsFromBackup error', result.data); + throw new Error('getNotImportedDonationsFromBackup error'); + } + return result.data.documents; + } + + async getSingleDonationFromBackupByTxHash( + txHash: string, + ): Promise { + const result = await axios.post( + `${baseUrl}findOne`, + { + collection: DONATION_SAVE_BACKUP_COLLECTION, + database: DONATION_SAVE_BACKUP_DATABASE, + dataSource: DONATION_SAVE_BACKUP_DATA_SOURCE, + filter: { + txHash, + }, + }, + { + headers: { + 'api-key': DONATION_SAVE_BACKUP_API_SECRET, + 'Content-Type': 'application/json', + 'Access-Control-Request-Headers': '*', + }, + }, + ); + + if (result.status !== 200) { + logger.error('getSingleDonationFromBackupByTxHash error', result.data); + throw new Error('getSingleDonationFromBackupByTxHash error'); + } + return result.data.document; + } + + async markDonationAsImported(donationMongoId: string): Promise { + const result = await axios.post( + `${baseUrl}updateOne`, + { + collection: DONATION_SAVE_BACKUP_COLLECTION, + database: DONATION_SAVE_BACKUP_DATABASE, + dataSource: DONATION_SAVE_BACKUP_DATA_SOURCE, + filter: { + _id: { $oid: donationMongoId }, + }, + update: { + $set: { + imported: true, + }, + }, + }, + { + headers: { + 'api-key': DONATION_SAVE_BACKUP_API_SECRET, + 'Content-Type': 'application/json', + 'Access-Control-Request-Headers': '*', + }, + }, + ); + + if (result.status !== 200) { + logger.error('markDonationAsImported error', result.data); + throw new Error('markDonationAsImported error'); + } + } + + async markDonationAsImportError( + donationMongoId: string, + errorMessage, + ): Promise { + const result = await axios.post( + `${baseUrl}updateOne`, + { + collection: DONATION_SAVE_BACKUP_COLLECTION, + database: DONATION_SAVE_BACKUP_DATABASE, + dataSource: DONATION_SAVE_BACKUP_DATA_SOURCE, + filter: { + _id: { $oid: donationMongoId }, + }, + update: { + $set: { + importError: errorMessage, + }, + }, + }, + { + headers: { + 'api-key': DONATION_SAVE_BACKUP_API_SECRET, + 'Content-Type': 'application/json', + 'Access-Control-Request-Headers': '*', + }, + }, + ); + + if (result.status !== 200) { + logger.error('markDonationAsImportError error', result.data); + throw new Error('markDonationAsImportError error'); + } + } + + async getSingleDonationFromBackupById( + donationMongoId: string, + ): Promise { + const result = await axios.post( + `${baseUrl}findOne`, + { + collection: DONATION_SAVE_BACKUP_COLLECTION, + database: DONATION_SAVE_BACKUP_DATABASE, + dataSource: DONATION_SAVE_BACKUP_DATA_SOURCE, + filter: { + _id: { $oid: donationMongoId }, + }, + }, + { + headers: { + 'api-key': DONATION_SAVE_BACKUP_API_SECRET, + 'Content-Type': 'application/json', + 'Access-Control-Request-Headers': '*', + }, + }, + ); + + if (result.status !== 200) { + logger.error('getSingleDonationFromBackupById error', result.data); + throw new Error('getSingleDonationFromBackupById error'); + } + return result.data.document; + } + + async unmarkDonationAsImported(donationMongoId: string): Promise { + const result = await axios.post( + `${baseUrl}updateOne`, + { + collection: DONATION_SAVE_BACKUP_COLLECTION, + database: DONATION_SAVE_BACKUP_DATABASE, + dataSource: DONATION_SAVE_BACKUP_DATA_SOURCE, + filter: { + _id: { $oid: donationMongoId }, + }, + update: { + $unset: { + imported: '', + }, + }, + }, + { + headers: { + 'api-key': DONATION_SAVE_BACKUP_API_SECRET, + 'Content-Type': 'application/json', + 'Access-Control-Request-Headers': '*', + }, + }, + ); + + if (result.status !== 200) { + logger.error('unmarkDonationAsImported error', result.data); + throw new Error('unmarkDonationAsImported error'); + } + } +} diff --git a/src/resolvers/donationResolver.ts b/src/resolvers/donationResolver.ts index d96979673..2895b7022 100644 --- a/src/resolvers/donationResolver.ts +++ b/src/resolvers/donationResolver.ts @@ -192,7 +192,8 @@ class DonationCurrencyStats { @Resolver(of => User) export class DonationResolver { - constructor(private readonly donationRepository: Repository) { + private readonly donationRepository: Repository; + constructor() { this.donationRepository = AppDataSource.getDataSource().getRepository(Donation); } diff --git a/src/server/bootstrap.ts b/src/server/bootstrap.ts index 8e062debc..5e3c5e96b 100644 --- a/src/server/bootstrap.ts +++ b/src/server/bootstrap.ts @@ -69,6 +69,7 @@ import { runCheckActiveStatusOfQfRounds } from '../services/cronJobs/checkActive import { runUpdateProjectCampaignsCacheJob } from '../services/cronJobs/updateProjectCampaignsCacheJob'; import { corsOptions } from './cors'; import { runSyncLostDonations } from '../services/cronJobs/importLostDonationsJob'; +import { runSyncBackupServiceDonations } from '../services/cronJobs/backupDonationImportJob'; Resource.validate = validate; @@ -340,6 +341,10 @@ export async function bootstrap() { runSyncLostDonations(); } + if ((config.get('ENABLE_IMPORT_DONATION_BACKUP') as string) === 'true') { + runSyncBackupServiceDonations(); + } + if ( (config.get('FILL_POWER_SNAPSHOT_BALANCE_SERVICE_ACTIVE') as string) === 'true' diff --git a/src/services/cronJobs/backupDonationImport.test.ts b/src/services/cronJobs/backupDonationImport.test.ts new file mode 100644 index 000000000..b88fd23a2 --- /dev/null +++ b/src/services/cronJobs/backupDonationImport.test.ts @@ -0,0 +1,85 @@ +import { createBackupDonation } from './backupDonationImportJob'; +import { + assertThrowsAsync, + createProjectData, + generateRandomEtheriumAddress, + generateRandomEvmTxHash, + generateTestAccessToken, + graphqlUrl, + saveProjectDirectlyToDb, +} from '../../../test/testUtils'; +import { User } from '../../entities/user'; +import { NETWORK_IDS } from '../../provider'; +import { assert } from 'chai'; +import { DONATION_STATUS } from '../../entities/donation'; +import { findTokenByNetworkAndSymbol } from '../../utils/tokenUtils'; + +describe('createBackupDonation test cases', createBackupDonationTestCases); + +function createBackupDonationTestCases() { + it('should create donation successfully', async () => { + const project = await saveProjectDirectlyToDb(createProjectData()); + const donorWalletAddress = generateRandomEtheriumAddress(); + await User.create({ + walletAddress: donorWalletAddress, + loginType: 'wallet', + firstName: 'first name', + }).save(); + const token = await findTokenByNetworkAndSymbol(NETWORK_IDS.XDAI, 'GIV'); + + const donation = await createBackupDonation({ + projectId: project.id, + chainId: NETWORK_IDS.XDAI, + txHash: generateRandomEvmTxHash(), + nonce: 1, + amount: 10, + _id: '65a90d86d3a1115b4ebc0731', + token: { + symbol: token.symbol, + address: token.address, + networkId: NETWORK_IDS.XDAI, + }, + anonymous: false, + symbol: 'GIV', + walletAddress: donorWalletAddress, + imported: false, + }); + assert.isOk(donation); + assert.isTrue(donation?.isTokenEligibleForGivback); + assert.equal(donation.status, DONATION_STATUS.PENDING); + + // should use input createdAt not now time + assert.equal(donation.createdAt.getTime(), 1705577862000); + }); + + it('should fail if projectId is invalid', async () => { + const donorWalletAddress = generateRandomEtheriumAddress(); + await User.create({ + walletAddress: donorWalletAddress, + loginType: 'wallet', + firstName: 'first name', + }).save(); + const token = await findTokenByNetworkAndSymbol(NETWORK_IDS.XDAI, 'GIV'); + + const badFunc = async () => { + await createBackupDonation({ + projectId: 99999999, + chainId: NETWORK_IDS.XDAI, + txHash: generateRandomEvmTxHash(), + nonce: 1, + amount: 10, + _id: '65a90d86d3a1115b4ebc0731', + token: { + symbol: token.symbol, + address: token.address, + networkId: NETWORK_IDS.XDAI, + }, + anonymous: false, + symbol: 'GIV', + walletAddress: donorWalletAddress, + imported: false, + }); + }; + await assertThrowsAsync(badFunc, 'Project not found.'); + }); +} diff --git a/src/services/cronJobs/backupDonationImportJob.ts b/src/services/cronJobs/backupDonationImportJob.ts new file mode 100644 index 000000000..a643b88ea --- /dev/null +++ b/src/services/cronJobs/backupDonationImportJob.ts @@ -0,0 +1,99 @@ +import config from '../../config'; + +import { logger } from '../../utils/logger'; +import { schedule } from 'node-cron'; +import { i18n, translationErrorMessagesKeys } from '../../utils/errorMessages'; +import { findUserByWalletAddress } from '../../repositories/userRepository'; +import { Donation } from '../../entities/donation'; +import { FetchedSavedFailDonationInterface } from '../../adapters/donationSaveBackup/DonationSaveBackupInterface'; +import { getDonationSaveBackupAdapter } from '../../adapters/adaptersFactory'; +import { DonationResolver } from '../../resolvers/donationResolver'; +import { ApolloContext } from '../../types/ApolloContext'; +import { findDonationById } from '../../repositories/donationRepository'; +import { getCreatedAtFromMongoObjectId } from '../../utils/utils'; + +const cronJobTime = + (config.get('DONATION_SAVE_BACKUP_CRONJOB_EXPRESSION') as string) || + '0 0 * * 0'; + +export const runSyncBackupServiceDonations = () => { + logger.debug('importBackupServiceDonations() has been called'); + schedule(cronJobTime, async () => { + await importBackupServiceDonations(); + }); +}; + +// Mock Mongo Methods to write a test +export const importBackupServiceDonations = async () => { + const limit = 10; + let donations = + await getDonationSaveBackupAdapter().getNotImportedDonationsFromBackup({ + limit, + }); + while (donations.length > 0) { + for (const donation of donations) { + try { + await createBackupDonation(donation); + await getDonationSaveBackupAdapter().markDonationAsImported( + donation._id, + ); + } catch (e) { + await getDonationSaveBackupAdapter().markDonationAsImportError( + donation._id, + e.message, + ); + logger.error(`donation error with id ${donation._id}: `, e); + logger.error('donation error with params: ', donation); + } + } + donations = + await getDonationSaveBackupAdapter().getNotImportedDonationsFromBackup({ + limit, + }); + } +}; + +// Same logic as the donationResolver CreateDonation() mutation +export const createBackupDonation = async ( + donationData: FetchedSavedFailDonationInterface, +): Promise => { + const { + amount, + txHash, + chainId, + token, + anonymous, + walletAddress, + projectId, + nonce, + safeTransactionId, + chainvineReferred, + } = donationData; + + const donorUser = await findUserByWalletAddress(walletAddress); + if (!donorUser) { + throw new Error(i18n.__(translationErrorMessagesKeys.UN_AUTHORIZED)); + } + + const donationResolver = new DonationResolver(); + const donationId = await donationResolver.createDonation( + amount, + txHash, + chainId, + token.address, + anonymous, + token.symbol, + projectId, + nonce, + '', + { + req: { user: { userId: donorUser.id }, auth: {} }, + } as ApolloContext, + chainvineReferred, + safeTransactionId, + ); + const donation = (await findDonationById(Number(donationId))) as Donation; + donation!.createdAt = getCreatedAtFromMongoObjectId(donationData._id); + + return donation.save(); +}; diff --git a/src/utils/errorMessages.ts b/src/utils/errorMessages.ts index 196afb6ad..d4d7179c7 100644 --- a/src/utils/errorMessages.ts +++ b/src/utils/errorMessages.ts @@ -135,6 +135,7 @@ export const errorMessages = { INVALID_WALLET_ADDRESS: 'Address not valid', INVALID_EMAIL: 'Email not valid', UN_AUTHORIZED: 'unAuthorized', + DONOR_USER_NOT_FOUND: 'DONOR_USER_NOT_FOUND', BOTH_FIRST_NAME_AND_LAST_NAME_CANT_BE_EMPTY: 'Both firstName and lastName cant be empty', FIRSTNAME_CANT_BE_EMPTY_STRING: 'firstName cant be empty string', @@ -293,6 +294,7 @@ export const translationErrorMessagesKeys = { INVALID_WALLET_ADDRESS: 'INVALID_WALLET_ADDRESS', INVALID_EMAIL: 'INVALID_EMAIL', UN_AUTHORIZED: 'UN_AUTHORIZED', + DONOR_USER_NOT_FOUND: 'DONOR_USER_NOT_FOUND', BOTH_FIRST_NAME_AND_LAST_NAME_CANT_BE_EMPTY: 'BOTH_FIRST_NAME_AND_LAST_NAME_CANT_BE_EMPTY', FIRSTNAME_CANT_BE_EMPTY_STRING: 'FIRSTNAME_CANT_BE_EMPTY_STRING', diff --git a/src/utils/utils.test.ts b/src/utils/utils.test.ts index bc295cba1..2ea812ea3 100644 --- a/src/utils/utils.test.ts +++ b/src/utils/utils.test.ts @@ -1,8 +1,12 @@ -import { getHtmlTextSummary } from './utils'; +import { getCreatedAtFromMongoObjectId, getHtmlTextSummary } from './utils'; import { assert } from 'chai'; import { SUMMARY_LENGTH } from '../constants/summary'; describe('getHtmlTextSummary test cases', getHtmlTextSummaryTestCases); +describe( + 'getCreatedAtFromMongoObjectId test cases', + getCreatedAtFromMongoObjectIdTestCases, +); function getHtmlTextSummaryTestCases() { it('should return empty string on negative length limit', () => { @@ -68,7 +72,7 @@ function getHtmlTextSummaryTestCases() { it('should remove images and link href', () => { const html = ` -

Home

Projects

GIVeconomy

Community

CREATE A PROJECT

0

Ram camp owner

Connected to xDAI


Ram third metamaskby Ram Meta


Iraq

Description

Updates0

Donations

Traces

What is your project about?How To Write A Great Project Description












DONATE


Traceable

GIVERS: 0

DONATIONS: 0

COMMUNITY

FOOD


1


Share

Be the first to give!

Your early support will go a long way and help inspire others to donate.


Home

Projects

About Us

FAQ

Support

Join Our Community

What is Giveth? 

User Guides 

Developer Docs 

Terms of Use

Giveth TRACE 

Commons Stack 

Partnerships

We\`'re Hiring! 










Support us with your donation

MMXXI - No Rights Reserved - The Giveth DAC


`; +

Home

Projects

GIVeconomy

Community

CREATE A PROJECT

0

Ram camp owner

Connected to xDAI


Ram third metamaskby Ram Meta


Iraq

Description

Updates0

Donations

Traces

What is your project about?How To Write A Great Project Description












DONATE


Traceable

GIVERS: 0

DONATIONS: 0

COMMUNITY

FOOD


1


Share

Be the first to give!

Your early support will go a long way and help inspire others to donate.


Home

Projects

About Us

FAQ

Support

Join Our Community

What is Giveth? 

User Guides 

Developer Docs 

Terms of Use

Giveth TRACE 

Commons Stack 

Partnerships

We\`'re Hiring! 










Support us with your donation

MMXXI - No Rights Reserved - The Giveth DAC


`; const expectedOutput = `Home Projects GIVeconomy @@ -114,3 +118,11 @@ MMXXI - No Rights Reserved - The Giveth DAC`; assert.equal(text, expectedOutput); }); } + +function getCreatedAtFromMongoObjectIdTestCases() { + // success scenario test case + it('should return correct date for valid mongo object id', () => { + const date = getCreatedAtFromMongoObjectId('65a90d86d3a1115b4ebc0731'); + assert.equal(date.getTime(), 1705577862000); + }); +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 74c5f0493..0e6a41062 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -420,3 +420,7 @@ export const dateToTimestampMs = (date: Date | string | number): number => { export function normalizeAmount(amount: string, decimals: number): number { return Number(amount) / 10 ** decimals; } + +export function getCreatedAtFromMongoObjectId(objectId: string): Date { + return new Date(parseInt(objectId.substring(0, 8), 16) * 1000); +} diff --git a/src/utils/validators/graphqlQueryValidators.ts b/src/utils/validators/graphqlQueryValidators.ts index 750a0cb87..217b62b97 100644 --- a/src/utils/validators/graphqlQueryValidators.ts +++ b/src/utils/validators/graphqlQueryValidators.ts @@ -121,7 +121,7 @@ export const createDonationQueryValidator = Joi.object({ projectId: Joi.number().integer().min(0).required(), nonce: Joi.number().integer().min(0).allow(null), anonymous: Joi.boolean(), - transakId: Joi.string(), + transakId: Joi.string()?.allow(null, ''), referrerId: Joi.string().allow(null, ''), safeTransactionId: Joi.string().allow(null, ''), chainType: Joi.string().required(), From 9c7d9cfa5f770a78eedb24984723b2f4ca4225e2 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Tue, 23 Jan 2024 12:59:39 +0330 Subject: [PATCH 27/34] Fix query of getting failed donations from mongo API --- .../donationSaveBackupAdapter.ts | 4 ++-- src/services/cronJobs/backupDonationImportJob.ts | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/adapters/donationSaveBackup/donationSaveBackupAdapter.ts b/src/adapters/donationSaveBackup/donationSaveBackupAdapter.ts index 7a48e6c3b..0d619842b 100644 --- a/src/adapters/donationSaveBackup/donationSaveBackupAdapter.ts +++ b/src/adapters/donationSaveBackup/donationSaveBackupAdapter.ts @@ -41,8 +41,8 @@ export class DonationSaveBackupAdapter implements DonationSaveBackupInterface { dataSource: DONATION_SAVE_BACKUP_DATA_SOURCE, limit: params.limit, filter: { - imported: { $ne: true }, - importError: { $ne: true }, + imported: { $exists: false }, + importError: { $exists: false }, }, sort: { _id: 1 }, }, diff --git a/src/services/cronJobs/backupDonationImportJob.ts b/src/services/cronJobs/backupDonationImportJob.ts index a643b88ea..43e422220 100644 --- a/src/services/cronJobs/backupDonationImportJob.ts +++ b/src/services/cronJobs/backupDonationImportJob.ts @@ -25,11 +25,16 @@ export const runSyncBackupServiceDonations = () => { // Mock Mongo Methods to write a test export const importBackupServiceDonations = async () => { + logger.info('importBackupServiceDonations() has been called'); const limit = 10; let donations = await getDonationSaveBackupAdapter().getNotImportedDonationsFromBackup({ limit, }); + logger.info( + 'importBackupServiceDonations() donations.length: ', + donations.length, + ); while (donations.length > 0) { for (const donation of donations) { try { @@ -37,6 +42,11 @@ export const importBackupServiceDonations = async () => { await getDonationSaveBackupAdapter().markDonationAsImported( donation._id, ); + logger.info('Failed donation has imported successfully', { + donationId: donation._id, + txHash: donation.txHash, + networkId: donation.chainId, + }); } catch (e) { await getDonationSaveBackupAdapter().markDonationAsImportError( donation._id, @@ -50,6 +60,10 @@ export const importBackupServiceDonations = async () => { await getDonationSaveBackupAdapter().getNotImportedDonationsFromBackup({ limit, }); + logger.info('importBackupServiceDonations() inside loop ', { + donationsLength: donations.length, + limit, + }); } }; From de235e54aaac23b71934c8b8d9b2efca9eec326b Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Tue, 23 Jan 2024 13:36:54 +0330 Subject: [PATCH 28/34] Add more logs for importing failed donations --- .../donationSaveBackupAdapter.ts | 53 +++++++++++-------- .../cronJobs/backupDonationImportJob.ts | 11 ++-- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/src/adapters/donationSaveBackup/donationSaveBackupAdapter.ts b/src/adapters/donationSaveBackup/donationSaveBackupAdapter.ts index 0d619842b..dea9bd4f8 100644 --- a/src/adapters/donationSaveBackup/donationSaveBackupAdapter.ts +++ b/src/adapters/donationSaveBackup/donationSaveBackupAdapter.ts @@ -33,33 +33,40 @@ export class DonationSaveBackupAdapter implements DonationSaveBackupInterface { async getNotImportedDonationsFromBackup(params: { limit: number; }): Promise { - const result = await axios.post( - `${baseUrl}find`, - { - collection: DONATION_SAVE_BACKUP_COLLECTION, - database: DONATION_SAVE_BACKUP_DATABASE, - dataSource: DONATION_SAVE_BACKUP_DATA_SOURCE, - limit: params.limit, - filter: { - imported: { $exists: false }, - importError: { $exists: false }, + try { + const result = await axios.post( + `${baseUrl}find`, + { + collection: DONATION_SAVE_BACKUP_COLLECTION, + database: DONATION_SAVE_BACKUP_DATABASE, + dataSource: DONATION_SAVE_BACKUP_DATA_SOURCE, + limit: params.limit, + filter: { + imported: { $exists: false }, + importError: { $exists: false }, + }, + sort: { _id: 1 }, }, - sort: { _id: 1 }, - }, - { - headers: { - 'api-key': DONATION_SAVE_BACKUP_API_SECRET, - 'Content-Type': 'application/json', - 'Access-Control-Request-Headers': '*', + { + headers: { + 'api-key': DONATION_SAVE_BACKUP_API_SECRET, + 'Content-Type': 'application/json', + 'Access-Control-Request-Headers': '*', + }, }, - }, - ); + ); - if (result.status !== 200) { - logger.error('getNotImportedDonationsFromBackup error', result.data); - throw new Error('getNotImportedDonationsFromBackup error'); + if (result.status !== 200) { + logger.error('getNotImportedDonationsFromBackup error', result.data); + throw new Error( + 'getNotImportedDonationsFromBackup error, status: ' + result.status, + ); + } + return result.data.documents; + } catch (e) { + logger.error('getNotImportedDonationsFromBackup error', e); + throw e; } - return result.data.documents; } async getSingleDonationFromBackupByTxHash( diff --git a/src/services/cronJobs/backupDonationImportJob.ts b/src/services/cronJobs/backupDonationImportJob.ts index 43e422220..f2364c9e0 100644 --- a/src/services/cronJobs/backupDonationImportJob.ts +++ b/src/services/cronJobs/backupDonationImportJob.ts @@ -17,7 +17,7 @@ const cronJobTime = '0 0 * * 0'; export const runSyncBackupServiceDonations = () => { - logger.debug('importBackupServiceDonations() has been called'); + logger.debug('runSyncBackupServiceDonations() has been called'); schedule(cronJobTime, async () => { await importBackupServiceDonations(); }); @@ -32,7 +32,7 @@ export const importBackupServiceDonations = async () => { limit, }); logger.info( - 'importBackupServiceDonations() donations.length: ', + 'importBackupServiceDonations() donations.length:', donations.length, ); while (donations.length > 0) { @@ -52,8 +52,11 @@ export const importBackupServiceDonations = async () => { donation._id, e.message, ); - logger.error(`donation error with id ${donation._id}: `, e); - logger.error('donation error with params: ', donation); + logger.error( + `Import failed donation error with id ${donation._id}: `, + e, + ); + logger.error('Import failed donation error with params: ', donation); } } donations = From c3e8c416b72d06fefd011a49d171335a8405129d Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Tue, 23 Jan 2024 14:02:19 +0330 Subject: [PATCH 29/34] Change info logs to debug logs --- .../notifications/MockNotificationAdapter.ts | 56 +++++++++---------- .../NotificationCenterAdapter.ts | 2 +- src/adapters/oauth2/twitterAdapter.ts | 6 +- src/repositories/instantBoostingRepository.ts | 2 +- src/repositories/qfRoundHistoryRepository.ts | 4 +- src/resolvers/donationResolver.ts | 2 +- src/routers/oauth2Callbacks.ts | 4 +- src/server/adminJs/adminJs.ts | 2 +- src/server/adminJs/tabs/projectsTab.ts | 6 +- src/services/Idriss/contractDonations.ts | 2 +- .../cronJobs/backupDonationImportJob.ts | 8 +-- .../cronJobs/importLostDonationsJob.ts | 24 ++++---- src/services/donationService.ts | 6 +- src/services/instantBoostingServices.ts | 8 +-- src/services/onramper/webhookHandler.ts | 2 +- .../poignArt/syncPoignArtDonationCronJob.ts | 2 +- 16 files changed, 68 insertions(+), 68 deletions(-) diff --git a/src/adapters/notifications/MockNotificationAdapter.ts b/src/adapters/notifications/MockNotificationAdapter.ts index ba973cdf2..da3a6d284 100644 --- a/src/adapters/notifications/MockNotificationAdapter.ts +++ b/src/adapters/notifications/MockNotificationAdapter.ts @@ -13,7 +13,7 @@ export class MockNotificationAdapter implements NotificationAdapterInterface { donation: Donation; project: Project; }): Promise { - logger.info('MockNotificationAdapter donationReceived', { + logger.debug('MockNotificationAdapter donationReceived', { projectSlug: params.project.slug, donationTxHash: params.donation.transactionId, donationNetworkId: params.donation.transactionNetworkId, @@ -26,7 +26,7 @@ export class MockNotificationAdapter implements NotificationAdapterInterface { project: Project; donor: User; }): Promise { - logger.info('MockNotificationAdapter donationSent', { + logger.debug('MockNotificationAdapter donationSent', { projectSlug: params.project.slug, donationTxHash: params.donation.transactionId, donationNetworkId: params.donation.transactionNetworkId, @@ -35,14 +35,14 @@ export class MockNotificationAdapter implements NotificationAdapterInterface { } projectVerified(params: { project: Project }): Promise { - logger.info('MockNotificationAdapter projectVerified', { + logger.debug('MockNotificationAdapter projectVerified', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); } projectBoosted(params: { projectId: number; userId: number }): Promise { - logger.info('MockNotificationAdapter projectBoosted', { + logger.debug('MockNotificationAdapter projectBoosted', { projectId: params.projectId, userId: params.userId, }); @@ -53,42 +53,42 @@ export class MockNotificationAdapter implements NotificationAdapterInterface { projectIds: number[]; userId: number; }): Promise { - logger.info('MockNotificationAdapter projectBoostedBatch', { + logger.debug('MockNotificationAdapter projectBoostedBatch', { projectIds: params.projectIds, userId: params.userId, }); return Promise.resolve(undefined); } projectBadgeRevoked(params: { project: Project }): Promise { - logger.info('MockNotificationAdapter projectBadgeRevoked', { + logger.debug('MockNotificationAdapter projectBadgeRevoked', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); } projectBadgeRevokeReminder(params: { project: Project }): Promise { - logger.info('MockNotificationAdapter projectBadgeRevokeReminder', { + logger.debug('MockNotificationAdapter projectBadgeRevokeReminder', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); } projectBadgeRevokeWarning(params: { project: Project }): Promise { - logger.info('MockNotificationAdapter projectBadgeRevokeWarning', { + logger.debug('MockNotificationAdapter projectBadgeRevokeWarning', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); } projectBadgeUpForRevoking(params: { project: Project }): Promise { - logger.info('MockNotificationAdapter projectBadgeUpForRevoking', { + logger.debug('MockNotificationAdapter projectBadgeUpForRevoking', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); } projectBadgeRevokeLastWarning(params: { project: Project }): Promise { - logger.info('MockNotificationAdapter projectBadgeRevokeLastWarning', { + logger.debug('MockNotificationAdapter projectBadgeRevokeLastWarning', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); @@ -98,28 +98,28 @@ export class MockNotificationAdapter implements NotificationAdapterInterface { project: Project; userId: number; }): Promise { - logger.info('MockNotificationAdapter projectReceivedHeartReaction', { + logger.debug('MockNotificationAdapter projectReceivedHeartReaction', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); } ProfileIsCompleted(params: { user: User }): Promise { - logger.info('MockNotificationAdapter ProfileIsCompleted', { + logger.debug('MockNotificationAdapter ProfileIsCompleted', { user: params.user, }); return Promise.resolve(undefined); } ProfileNeedToBeCompleted(params: { user: User }): Promise { - logger.info('MockNotificationAdapter ProfileNeedToBeCompleted', { + logger.debug('MockNotificationAdapter ProfileNeedToBeCompleted', { user: params.user, }); return Promise.resolve(undefined); } projectCancelled(params: { project: Project }): Promise { - logger.info('MockNotificationAdapter projectCancelled', { + logger.debug('MockNotificationAdapter projectCancelled', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); @@ -128,76 +128,76 @@ export class MockNotificationAdapter implements NotificationAdapterInterface { project: Project; update: string; }): Promise { - logger.info('MockNotificationAdapter projectUpdateAdded', { + logger.debug('MockNotificationAdapter projectUpdateAdded', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); } projectDeListed(params: { project: Project }): Promise { - logger.info('MockNotificationAdapter projectDeListed', { + logger.debug('MockNotificationAdapter projectDeListed', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); } projectDeactivated(params: { project: Project }): Promise { - logger.info('MockNotificationAdapter projectDeactivated', { + logger.debug('MockNotificationAdapter projectDeactivated', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); } projectListed(params: { project: Project }): Promise { - logger.info('MockNotificationAdapter projectListed', { + logger.debug('MockNotificationAdapter projectListed', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); } projectPublished(params: { project: Project }): Promise { - logger.info('MockNotificationAdapter projectPublished', { + logger.debug('MockNotificationAdapter projectPublished', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); } projectEdited(params: { project: Project }): Promise { - logger.info('MockNotificationAdapter projectEdited', { + logger.debug('MockNotificationAdapter projectEdited', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); } projectGotDraftByAdmin(params: { project: Project }): Promise { - logger.info('MockNotificationAdapter projectGotDraftByAdmin', { + logger.debug('MockNotificationAdapter projectGotDraftByAdmin', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); } projectReactivated(params: { project: Project }): Promise { - logger.info('MockNotificationAdapter projectReactivated', { + logger.debug('MockNotificationAdapter projectReactivated', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); } projectSavedAsDraft(params: { project: Project }): Promise { - logger.info('MockNotificationAdapter projectSavedAsDraft', { + logger.debug('MockNotificationAdapter projectSavedAsDraft', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); } projectUnVerified(params: { project: Project }): Promise { - logger.info('MockNotificationAdapter projectUnVerified', { + logger.debug('MockNotificationAdapter projectUnVerified', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); } verificationFormRejected(params: { project: Project }): Promise { - logger.info('MockNotificationAdapter verificationFormRejected', { + logger.debug('MockNotificationAdapter verificationFormRejected', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); @@ -207,7 +207,7 @@ export class MockNotificationAdapter implements NotificationAdapterInterface { project: Project; donationInfo: { txLink: string; reason: string }; }): Promise { - logger.info('MockNotificationAdapter donationGetPriceFailed', { + logger.debug('MockNotificationAdapter donationGetPriceFailed', { projectSlug: params.project.slug, }); return Promise.resolve(undefined); @@ -216,12 +216,12 @@ export class MockNotificationAdapter implements NotificationAdapterInterface { broadcastNotification( params: BroadCastNotificationInputParams, ): Promise { - logger.info('MockNotificationAdapter broadcastNotification', params); + logger.debug('MockNotificationAdapter broadcastNotification', params); return Promise.resolve(undefined); } projectsHaveNewRank(params: ProjectsHaveNewRankingInputParam): Promise { - logger.info('MockNotificationAdapter projectHasNewRank', { + logger.debug('MockNotificationAdapter projectHasNewRank', { params, }); return Promise.resolve(undefined); diff --git a/src/adapters/notifications/NotificationCenterAdapter.ts b/src/adapters/notifications/NotificationCenterAdapter.ts index aeccaed47..2bcb65225 100644 --- a/src/adapters/notifications/NotificationCenterAdapter.ts +++ b/src/adapters/notifications/NotificationCenterAdapter.ts @@ -842,7 +842,7 @@ export class NotificationCenterAdapter implements NotificationAdapterInterface { } else if (param.newRank > param.oldRank) { eventName = NOTIFICATIONS_EVENT_NAMES.PROJECT_HAS_A_NEW_RANK; } - logger.info('send rank changed notification ', { + logger.debug('send rank changed notification ', { eventName, slug: project.slug, newRank: param.newRank, diff --git a/src/adapters/oauth2/twitterAdapter.ts b/src/adapters/oauth2/twitterAdapter.ts index e10684772..2d21e2988 100644 --- a/src/adapters/oauth2/twitterAdapter.ts +++ b/src/adapters/oauth2/twitterAdapter.ts @@ -29,11 +29,11 @@ export class TwitterAdapter implements SocialNetworkOauth2AdapterInterface { oauth2Code: string; }): Promise { try { - logger.info('getUserInfoByOauth2Code code', params.oauth2Code); + logger.debug('getUserInfoByOauth2Code code', params.oauth2Code); const accessToken = await this.authClient.requestAccessToken( params.oauth2Code, ); - logger.info('getUserInfoByOauth2Code accessToken', accessToken); + logger.debug('getUserInfoByOauth2Code accessToken', accessToken); // https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-me#tab0 const meResult = await axios.get('https://api.twitter.com/2/users/me', { @@ -41,7 +41,7 @@ export class TwitterAdapter implements SocialNetworkOauth2AdapterInterface { Authorization: `Bearer ${accessToken.token.access_token}`, }, }); - logger.info('getUserInfoByOauth2Code meResult', meResult.data); + logger.debug('getUserInfoByOauth2Code meResult', meResult.data); /** * sample response * { diff --git a/src/repositories/instantBoostingRepository.ts b/src/repositories/instantBoostingRepository.ts index 12d70a4f6..7a5cd210e 100644 --- a/src/repositories/instantBoostingRepository.ts +++ b/src/repositories/instantBoostingRepository.ts @@ -37,7 +37,7 @@ export const getUsersBoostedWithoutInstanceBalance = async ( limit = 50, offset = 0, ): Promise<{ id: number; walletAddress: string }[]> => { - logger.info('getUsersBoostedWithoutBalance', { limit, offset }); + logger.debug('getUsersBoostedWithoutBalance', { limit, offset }); return await AppDataSource.getDataSource().query( ` SELECT ID, "walletAddress" FROM PUBLIC.USER diff --git a/src/repositories/qfRoundHistoryRepository.ts b/src/repositories/qfRoundHistoryRepository.ts index 1b6bef427..cfa25ccf8 100644 --- a/src/repositories/qfRoundHistoryRepository.ts +++ b/src/repositories/qfRoundHistoryRepository.ts @@ -4,7 +4,7 @@ import { logger } from '../utils/logger'; export const fillQfRoundHistory = async (): Promise => { try { - logger.info('fillQfRoundHistory() has been called'); + logger.debug('fillQfRoundHistory() has been called'); await AppDataSource.getDataSource().query(` INSERT INTO qf_round_history ("projectId", "qfRoundId", "uniqueDonors", "raisedFundInUsd", "donationsCount", "createdAt", "updatedAt") SELECT @@ -25,7 +25,7 @@ export const fillQfRoundHistory = async (): Promise => { GROUP BY d."projectId", d."qfRoundId" ON CONFLICT ("projectId", "qfRoundId") DO NOTHING; `); - logger.info('fillQfRoundHistory() query executed successfully'); + logger.debug('fillQfRoundHistory() query executed successfully'); } catch (e) { logger.error('fillQfRoundHistory() error: ', e); } diff --git a/src/resolvers/donationResolver.ts b/src/resolvers/donationResolver.ts index 2895b7022..e1f0f0bd9 100644 --- a/src/resolvers/donationResolver.ts +++ b/src/resolvers/donationResolver.ts @@ -619,7 +619,7 @@ export class DonationResolver { referrerId, userId: ctx?.req?.user?.userId, }; - logger.info( + logger.debug( 'createDonation() resolver has been called with this data', logData, ); diff --git a/src/routers/oauth2Callbacks.ts b/src/routers/oauth2Callbacks.ts index 0398b1c04..634bb803c 100644 --- a/src/routers/oauth2Callbacks.ts +++ b/src/routers/oauth2Callbacks.ts @@ -23,7 +23,7 @@ const generateDappVerificationUrl = async (params: { projectVerificationId, ); const address = `${dappBaseUrl}/verification/${projectVerificationForm?.project?.slug}?success=${success}&message=${message}`; - logger.info('generateDappVerificationUrl ', { + logger.debug('generateDappVerificationUrl ', { params, address, }); @@ -35,7 +35,7 @@ oauth2CallbacksRouter.get( async (request: Request, response: Response) => { let projectVerificationId; try { - logger.info('/callback/discord pramas', { + logger.debug('/callback/discord pramas', { query: request.query, url: request.url, params: request.params, diff --git a/src/server/adminJs/adminJs.ts b/src/server/adminJs/adminJs.ts index 342e16364..2001d36c0 100644 --- a/src/server/adminJs/adminJs.ts +++ b/src/server/adminJs/adminJs.ts @@ -163,7 +163,7 @@ const getResources = async (): Promise => { params, }; - logger.info('AdminJs Log', JSON.stringify(log, null, 2)); + logger.debug('AdminJs Log', JSON.stringify(log, null, 2)); return response; }; diff --git a/src/server/adminJs/tabs/projectsTab.ts b/src/server/adminJs/tabs/projectsTab.ts index a24325760..677549ee2 100644 --- a/src/server/adminJs/tabs/projectsTab.ts +++ b/src/server/adminJs/tabs/projectsTab.ts @@ -228,7 +228,7 @@ export const verifyProjects = async ( projectsBeforeUpdating.find(p => p.id === project.id)?.verified === verificationStatus ) { - logger.info('verifying/unVerifying project but no changes happened', { + logger.debug('verifying/unVerifying project but no changes happened', { projectId: project.id, verificationStatus, }); @@ -334,7 +334,7 @@ export const updateStatusOfProjects = async ( projectsBeforeUpdating.find(p => p.id === project.id)?.statusId === projectStatus.id ) { - logger.info('Changing project status but no changes happened', { + logger.debug('Changing project status but no changes happened', { projectId: project.id, projectStatus, }); @@ -562,7 +562,7 @@ export const listDelist = async ( projectsBeforeUpdating.find(p => p.id === project.id)?.reviewStatus === reviewStatus ) { - logger.info('listing/uListing project but no changes happened', { + logger.debug('listing/uListing project but no changes happened', { projectId: project.id, reviewStatus, }); diff --git a/src/services/Idriss/contractDonations.ts b/src/services/Idriss/contractDonations.ts index f91aa21c1..9edca7bc1 100644 --- a/src/services/Idriss/contractDonations.ts +++ b/src/services/Idriss/contractDonations.ts @@ -91,7 +91,7 @@ export const getTwitterDonations = async () => { try { // Check if recipient is a relevant Giveth recipient if (relevantRecipients.includes(event.recipientAddress.toLowerCase())) { - logger.info( + logger.debug( 'Creating Donation from Idriss' + event.recipientAddress.toLowerCase(), ); diff --git a/src/services/cronJobs/backupDonationImportJob.ts b/src/services/cronJobs/backupDonationImportJob.ts index f2364c9e0..055ed0b69 100644 --- a/src/services/cronJobs/backupDonationImportJob.ts +++ b/src/services/cronJobs/backupDonationImportJob.ts @@ -25,13 +25,13 @@ export const runSyncBackupServiceDonations = () => { // Mock Mongo Methods to write a test export const importBackupServiceDonations = async () => { - logger.info('importBackupServiceDonations() has been called'); + logger.debug('importBackupServiceDonations() has been called'); const limit = 10; let donations = await getDonationSaveBackupAdapter().getNotImportedDonationsFromBackup({ limit, }); - logger.info( + logger.debug( 'importBackupServiceDonations() donations.length:', donations.length, ); @@ -42,7 +42,7 @@ export const importBackupServiceDonations = async () => { await getDonationSaveBackupAdapter().markDonationAsImported( donation._id, ); - logger.info('Failed donation has imported successfully', { + logger.debug('Failed donation has imported successfully', { donationId: donation._id, txHash: donation.txHash, networkId: donation.chainId, @@ -63,7 +63,7 @@ export const importBackupServiceDonations = async () => { await getDonationSaveBackupAdapter().getNotImportedDonationsFromBackup({ limit, }); - logger.info('importBackupServiceDonations() inside loop ', { + logger.debug('importBackupServiceDonations() inside loop ', { donationsLength: donations.length, limit, }); diff --git a/src/services/cronJobs/importLostDonationsJob.ts b/src/services/cronJobs/importLostDonationsJob.ts index 5065a5c8c..7e550fe6f 100644 --- a/src/services/cronJobs/importLostDonationsJob.ts +++ b/src/services/cronJobs/importLostDonationsJob.ts @@ -64,7 +64,7 @@ export const importLostDonations = async () => { for (const tx of donationTxHashes) { try { - logger.info('processing txhash: ', tx); + logger.debug('processing txhash: ', tx); const donationExists = await Donation.createQueryBuilder('donation') .where(`lower(donation.transactionId) = :hash`, { hash: tx.toLowerCase(), @@ -76,7 +76,7 @@ export const importLostDonations = async () => { const transaction = await getProvider(networkId).getTransaction(tx); if (!transaction) { // Transaction not found - logger.info('transaction not found for tx: ', tx); + logger.debug('transaction not found for tx: ', tx); continue; } @@ -84,7 +84,7 @@ export const importLostDonations = async () => { if (!receipt) { // Transaction is not mined yet // https://web3js.readthedocs.io/en/v1.2.0/web3-eth.html#gettransactionreceipt - logger.info('receipt not found for tx: ', tx); + logger.debug('receipt not found for tx: ', tx); continue; } @@ -104,11 +104,11 @@ export const importLostDonations = async () => { .getOne(); if (!dbUser) { - logger.info('user not found for tx: ', tx); + logger.debug('user not found for tx: ', tx); continue; // User does not exist on giveth, not a UI donation, skip } - logger.info('token address searched: ', erc20Token); + logger.debug('token address searched: ', erc20Token); // Check if its an ERC-20 Token let tokenInDB; @@ -153,14 +153,14 @@ export const importLostDonations = async () => { nativeToken!, ); } else { - logger.info('transaction type not valid for tx: ', tx); + logger.debug('transaction type not valid for tx: ', tx); continue; // Not a transaction recognized by our logic } - logger.info('token being searched: ', tokenInDB?.id); + logger.debug('token being searched: ', tokenInDB?.id); if (!donationParams) { - logger.info('Params invalid for tx: ', tx); + logger.debug('Params invalid for tx: ', tx); continue; } @@ -174,7 +174,7 @@ export const importLostDonations = async () => { .getOne(); if (!project) { - logger.info('Project not found for tx: ', tx); + logger.debug('Project not found for tx: ', tx); continue; // project doesn't exist on giveth, skip donation } @@ -195,7 +195,7 @@ export const importLostDonations = async () => { date: donationDateCoingeckoFormat, }); } catch (e) { - logger.info('CoingeckoPrice not found for tx: ', tx); + logger.debug('CoingeckoPrice not found for tx: ', tx); logger.error('importLostDonations() coingecko error', e); } @@ -236,8 +236,8 @@ export const importLostDonations = async () => { await dbDonation.save(); } catch (e) { - logger.info('Error saving donation for for tx: ', tx); - logger.info('Error saving donation: ', e); + logger.debug('Error saving donation for for tx: ', tx); + logger.debug('Error saving donation: ', e); } await updateUserTotalDonated(dbUser.id); diff --git a/src/services/donationService.ts b/src/services/donationService.ts index 3e9573676..491f55e13 100644 --- a/src/services/donationService.ts +++ b/src/services/donationService.ts @@ -55,7 +55,7 @@ export const updateDonationPricesAndValues = async ( priceChainId: number, amount: string | number, ) => { - logger.info('updateDonationPricesAndValues() has been called', { + logger.debug('updateDonationPricesAndValues() has been called', { donationId: donation.id, projectId: project.id, token: token?.symbol, @@ -120,7 +120,7 @@ export const updateDonationPricesAndValues = async ( }, ); } - logger.info('updateDonationPricesAndValues() result', { + logger.debug('updateDonationPricesAndValues() result', { valueUsd: donation.valueUsd, donationId: donation.id, projectId: project.id, @@ -401,7 +401,7 @@ export const syncDonationStatusWithBlockchainNetwork = async (params: { // send chainvine the referral as last step to not interrupt previous if (donation.referrerWallet && donation.isReferrerGivbackEligible) { - logger.info( + logger.debug( 'sending chainvine params: ', JSON.stringify({ fromWalletAddress: donation.fromWalletAddress, diff --git a/src/services/instantBoostingServices.ts b/src/services/instantBoostingServices.ts index 010755b9f..34ef04dda 100644 --- a/src/services/instantBoostingServices.ts +++ b/src/services/instantBoostingServices.ts @@ -33,7 +33,7 @@ export const updateInstantBoosting = async (): Promise => { export const updateInstantPowerBalances = async ( customGivPowerBalanceAggregator?: IGivPowerBalanceAggregator, ): Promise => { - logger.info('Update instant power balances...'); + logger.debug('Update instant power balances...'); const givPowerSubgraphAdapter = customGivPowerBalanceAggregator || getPowerBalanceAggregatorAdapter(); await fetchUpdatedInstantPowerBalances(givPowerSubgraphAdapter); @@ -47,7 +47,7 @@ export const updateInstantPowerBalances = async ( const fetchUpdatedInstantPowerBalances = async ( givPowerBalanceAggregator: IGivPowerBalanceAggregator, ): Promise => { - logger.info('1. Fetch updated instant powers'); + logger.debug('1. Fetch updated instant powers'); // Let's save it now to sync all balances till this point // const [latestSubgraphIndexBlock, latestSyncedBlock] = await Promise.all([ // givPowerBalanceAggregator.getLatestIndexedBlockInfo(), @@ -80,7 +80,7 @@ const fetchUpdatedInstantPowerBalances = async ( const instances = boosterUsers.map(user => { const walletAddress = user.walletAddress!.toLowerCase(); const { balance, updatedAt } = addressBalanceMap[walletAddress]; - logger.info( + logger.debug( `Update user ${user.id} - ${walletAddress} instant power balance to ${balance} - updateAt ${updatedAt}`, ); return { @@ -162,7 +162,7 @@ const fillMissingInstantPowerBalances = async ( Partial >((item): Partial => { const { balance, updatedAt } = addressBalanceMap[item.walletAddress]; - logger.info( + logger.debug( `Update user ${item.id} - ${item.walletAddress} instant power balance to ${balance} - updateAt ${updatedAt}`, ); return { diff --git a/src/services/onramper/webhookHandler.ts b/src/services/onramper/webhookHandler.ts index a80260393..8ad07512f 100644 --- a/src/services/onramper/webhookHandler.ts +++ b/src/services/onramper/webhookHandler.ts @@ -34,7 +34,7 @@ export async function onramperWebhookHandler(request, response) { await createFiatDonationFromOnramper(fiatTransaction); } - logger.info( + logger.debug( 'User Onramper Transaction Arrived', JSON.stringify({ type: fiatTransaction.type, diff --git a/src/services/poignArt/syncPoignArtDonationCronJob.ts b/src/services/poignArt/syncPoignArtDonationCronJob.ts index 0a4f4ade5..527367b48 100644 --- a/src/services/poignArt/syncPoignArtDonationCronJob.ts +++ b/src/services/poignArt/syncPoignArtDonationCronJob.ts @@ -51,7 +51,7 @@ const importPoignArtDonations = async () => { ) .getOne(); if (!unchainProject) { - logger.info( + logger.debug( `importPoignArtDonations() There is no project with walletAddress of ${unchainProjectAddress} in our db`, ); return; From 028aefad077adf9c1303778f8f367c5114ff23aa Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Tue, 23 Jan 2024 14:44:57 +0330 Subject: [PATCH 30/34] 1.21.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a62d9ef2b..71b58b832 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "giveth-graphql-api", - "version": "1.20.0", + "version": "1.21.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "giveth-graphql-api", - "version": "1.20.0", + "version": "1.21.0", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index 5547ef687..854685da0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "giveth-graphql-api", - "version": "1.20.0", + "version": "1.21.0", "description": "Backend GraphQL server for Giveth originally forked from Topia", "main": "./dist/index.js", "dependencies": { From 56f5efc0cc39cd2d20d58510904c166f76f71b75 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Tue, 23 Jan 2024 16:01:19 +0330 Subject: [PATCH 31/34] Add importDate to donation entity --- ...12969-add_import_date_to_donation_entity.ts | 13 +++++++++++++ src/entities/donation.ts | 4 ++++ src/repositories/donationRepository.ts | 18 +++++++++++++----- .../cronJobs/backupDonationImport.test.ts | 2 +- .../cronJobs/backupDonationImportJob.ts | 2 +- 5 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 migration/1706012712969-add_import_date_to_donation_entity.ts diff --git a/migration/1706012712969-add_import_date_to_donation_entity.ts b/migration/1706012712969-add_import_date_to_donation_entity.ts new file mode 100644 index 000000000..e5b278480 --- /dev/null +++ b/migration/1706012712969-add_import_date_to_donation_entity.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class addImportDateToDonationEntity1706012712969 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "donation" ADD COLUMN "importDate" TIMESTAMP WITH TIME ZONE`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "donation" DROP COLUMN "importDate"`); + } + +} diff --git a/src/entities/donation.ts b/src/entities/donation.ts index 394fee0e9..f4c0860b9 100644 --- a/src/entities/donation.ts +++ b/src/entities/donation.ts @@ -214,6 +214,10 @@ export class Donation extends BaseEntity { @Column() createdAt: Date; + @Field(type => Date, { nullable: true }) + @Column({ nullable: true }) + importDate: Date; + @Field(type => String, { nullable: true }) @Column({ nullable: true }) donationType?: string; diff --git a/src/repositories/donationRepository.ts b/src/repositories/donationRepository.ts index f6b34e823..af3faf85d 100644 --- a/src/repositories/donationRepository.ts +++ b/src/repositories/donationRepository.ts @@ -387,12 +387,20 @@ export const getPendingDonationsIds = (): Promise<{ id: number }[]> => { hours: Number(process.env.DONATION_VERIFICAITON_EXPIRATION_HOURS), }) .toDate(); + return Donation.find({ - where: { - status: DONATION_STATUS.PENDING, - isFiat: false, - createdAt: MoreThan(date), - }, + where: [ + { + status: DONATION_STATUS.PENDING, + isFiat: false, + createdAt: MoreThan(date), + }, + { + status: DONATION_STATUS.PENDING, + isFiat: false, + importDate: MoreThan(date), + }, + ], select: ['id'], }); }; diff --git a/src/services/cronJobs/backupDonationImport.test.ts b/src/services/cronJobs/backupDonationImport.test.ts index b88fd23a2..2b03573c6 100644 --- a/src/services/cronJobs/backupDonationImport.test.ts +++ b/src/services/cronJobs/backupDonationImport.test.ts @@ -49,7 +49,7 @@ function createBackupDonationTestCases() { assert.equal(donation.status, DONATION_STATUS.PENDING); // should use input createdAt not now time - assert.equal(donation.createdAt.getTime(), 1705577862000); + assert.equal(donation.importDate.getTime(), 1705577862000); }); it('should fail if projectId is invalid', async () => { diff --git a/src/services/cronJobs/backupDonationImportJob.ts b/src/services/cronJobs/backupDonationImportJob.ts index 055ed0b69..9c8595939 100644 --- a/src/services/cronJobs/backupDonationImportJob.ts +++ b/src/services/cronJobs/backupDonationImportJob.ts @@ -110,7 +110,7 @@ export const createBackupDonation = async ( safeTransactionId, ); const donation = (await findDonationById(Number(donationId))) as Donation; - donation!.createdAt = getCreatedAtFromMongoObjectId(donationData._id); + donation!.importDate = getCreatedAtFromMongoObjectId(donationData._id); return donation.save(); }; From c91719c4073c7d5af4b07b5ccb7118d3ab0624b1 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Tue, 23 Jan 2024 16:16:54 +0330 Subject: [PATCH 32/34] Fill importDate of donation correctly --- ...2969-add_import_date_to_donation_entity.ts | 26 ++++++++++--------- .../cronJobs/backupDonationImport.test.ts | 2 +- .../cronJobs/backupDonationImportJob.ts | 3 ++- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/migration/1706012712969-add_import_date_to_donation_entity.ts b/migration/1706012712969-add_import_date_to_donation_entity.ts index e5b278480..33fbf0470 100644 --- a/migration/1706012712969-add_import_date_to_donation_entity.ts +++ b/migration/1706012712969-add_import_date_to_donation_entity.ts @@ -1,13 +1,15 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class addImportDateToDonationEntity1706012712969 implements MigrationInterface { - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "donation" ADD COLUMN "importDate" TIMESTAMP WITH TIME ZONE`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "donation" DROP COLUMN "importDate"`); - } - +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class addImportDateToDonationEntity1706012712969 + implements MigrationInterface +{ + async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "donation" ADD COLUMN IF NOT EXISTS "importDate" TIMESTAMP WITH TIME ZONE`, + ); + } + + async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "donation" DROP COLUMN "importDate"`); + } } diff --git a/src/services/cronJobs/backupDonationImport.test.ts b/src/services/cronJobs/backupDonationImport.test.ts index 2b03573c6..b88fd23a2 100644 --- a/src/services/cronJobs/backupDonationImport.test.ts +++ b/src/services/cronJobs/backupDonationImport.test.ts @@ -49,7 +49,7 @@ function createBackupDonationTestCases() { assert.equal(donation.status, DONATION_STATUS.PENDING); // should use input createdAt not now time - assert.equal(donation.importDate.getTime(), 1705577862000); + assert.equal(donation.createdAt.getTime(), 1705577862000); }); it('should fail if projectId is invalid', async () => { diff --git a/src/services/cronJobs/backupDonationImportJob.ts b/src/services/cronJobs/backupDonationImportJob.ts index 9c8595939..5e4d13de5 100644 --- a/src/services/cronJobs/backupDonationImportJob.ts +++ b/src/services/cronJobs/backupDonationImportJob.ts @@ -110,7 +110,8 @@ export const createBackupDonation = async ( safeTransactionId, ); const donation = (await findDonationById(Number(donationId))) as Donation; - donation!.importDate = getCreatedAtFromMongoObjectId(donationData._id); + donation!.createdAt = getCreatedAtFromMongoObjectId(donationData._id); + donation!.importDate = new Date(); return donation.save(); }; From e2d52b4be709005ec5c18e2da38df007fbfb5fb3 Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Tue, 23 Jan 2024 17:08:40 +0330 Subject: [PATCH 33/34] Put importing failed donations in try...catch --- .../cronJobs/backupDonationImportJob.ts | 82 ++++++++++--------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/src/services/cronJobs/backupDonationImportJob.ts b/src/services/cronJobs/backupDonationImportJob.ts index 5e4d13de5..7d1476ed1 100644 --- a/src/services/cronJobs/backupDonationImportJob.ts +++ b/src/services/cronJobs/backupDonationImportJob.ts @@ -25,48 +25,52 @@ export const runSyncBackupServiceDonations = () => { // Mock Mongo Methods to write a test export const importBackupServiceDonations = async () => { - logger.debug('importBackupServiceDonations() has been called'); - const limit = 10; - let donations = - await getDonationSaveBackupAdapter().getNotImportedDonationsFromBackup({ - limit, - }); - logger.debug( - 'importBackupServiceDonations() donations.length:', - donations.length, - ); - while (donations.length > 0) { - for (const donation of donations) { - try { - await createBackupDonation(donation); - await getDonationSaveBackupAdapter().markDonationAsImported( - donation._id, - ); - logger.debug('Failed donation has imported successfully', { - donationId: donation._id, - txHash: donation.txHash, - networkId: donation.chainId, - }); - } catch (e) { - await getDonationSaveBackupAdapter().markDonationAsImportError( - donation._id, - e.message, - ); - logger.error( - `Import failed donation error with id ${donation._id}: `, - e, - ); - logger.error('Import failed donation error with params: ', donation); - } - } - donations = + try { + logger.debug('importBackupServiceDonations() has been called'); + const limit = 10; + let donations = await getDonationSaveBackupAdapter().getNotImportedDonationsFromBackup({ limit, }); - logger.debug('importBackupServiceDonations() inside loop ', { - donationsLength: donations.length, - limit, - }); + logger.debug( + 'importBackupServiceDonations() donations.length:', + donations.length, + ); + while (donations.length > 0) { + for (const donation of donations) { + try { + await createBackupDonation(donation); + await getDonationSaveBackupAdapter().markDonationAsImported( + donation._id, + ); + logger.debug('Failed donation has imported successfully', { + donationId: donation._id, + txHash: donation.txHash, + networkId: donation.chainId, + }); + } catch (e) { + await getDonationSaveBackupAdapter().markDonationAsImportError( + donation._id, + e.message, + ); + logger.error( + `Import failed donation error with id ${donation._id}: `, + e, + ); + logger.error('Import failed donation error with params: ', donation); + } + } + donations = + await getDonationSaveBackupAdapter().getNotImportedDonationsFromBackup({ + limit, + }); + logger.debug('importBackupServiceDonations() inside loop ', { + donationsLength: donations.length, + limit, + }); + } + } catch (e) { + logger.error('importBackupServiceDonations() error: ', e); } }; From 550db9c0536ab34006a8fe5f20f31bbeaa4767f6 Mon Sep 17 00:00:00 2001 From: Amin Latifi Date: Sun, 28 Jan 2024 17:14:15 +0330 Subject: [PATCH 34/34] Added back jobId to verifyDonationsQueue, along with removeOnComplete and removeOnFail --- src/services/cronJobs/syncDonationsWithNetwork.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/cronJobs/syncDonationsWithNetwork.ts b/src/services/cronJobs/syncDonationsWithNetwork.ts index da2850d86..7ff03b7aa 100644 --- a/src/services/cronJobs/syncDonationsWithNetwork.ts +++ b/src/services/cronJobs/syncDonationsWithNetwork.ts @@ -54,7 +54,9 @@ const addJobToCheckPendingDonationsWithNetwork = async () => { donationId: donation.id, }, { - // jobId: `verify-donation-id-${donation.id}`, + jobId: `verify-donation-id-${donation.id}`, + removeOnComplete: true, + removeOnFail: true, }, ); });