diff --git a/migration/1725188424424-UniqueProjectAdressWithMomoForStellar.ts b/migration/1725188424424-UniqueProjectAdressWithMomoForStellar.ts new file mode 100644 index 000000000..53c6aa47b --- /dev/null +++ b/migration/1725188424424-UniqueProjectAdressWithMomoForStellar.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UniqueProjectAdressWithMomoForStellar1725188424424 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE UNIQUE INDEX unique_stellar_address + ON project_address (address, memo) + WHERE "chainType" = 'STELLAR'; + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP INDEX unique_stellar_address; + `); + } +} diff --git a/src/entities/draftDonation.ts b/src/entities/draftDonation.ts index e25c716f3..5a48c1c52 100644 --- a/src/entities/draftDonation.ts +++ b/src/entities/draftDonation.ts @@ -120,7 +120,7 @@ export class DraftDonation extends BaseEntity { @Column({ nullable: true }) relevantDonationTxHash?: string; - @Field() + @Field(_type => String, { nullable: true }) @Column({ nullable: true }) toWalletMemo?: string; diff --git a/src/repositories/projectAddressRepository.ts b/src/repositories/projectAddressRepository.ts index b2c3078e2..67c85517e 100644 --- a/src/repositories/projectAddressRepository.ts +++ b/src/repositories/projectAddressRepository.ts @@ -40,6 +40,7 @@ export const isWalletAddressInPurpleList = async ( export const findRelatedAddressByWalletAddress = async ( walletAddress: string, chainType?: ChainType, + memo?: string, ) => { let query = ProjectAddress.createQueryBuilder('projectAddress'); @@ -50,9 +51,24 @@ export const findRelatedAddressByWalletAddress = async ( }); break; case ChainType.STELLAR: - query = query.where(`UPPER(address) = :walletAddress`, { - walletAddress: walletAddress.toUpperCase(), - }); + // If a memo is provided, check for both address and memo + if (memo) { + query = query.where( + 'UPPER(address) = :walletAddress AND memo = :memo', + { + walletAddress: walletAddress.toUpperCase(), + memo, + }, + ); + } else { + // If no memo is provided, check only the address + query = query.where( + 'UPPER(address) = :walletAddress AND memo IS NULL', + { + walletAddress: walletAddress.toUpperCase(), + }, + ); + } break; case ChainType.EVM: default: diff --git a/src/resolvers/projectResolver.ts b/src/resolvers/projectResolver.ts index c7513b4b4..4a315ae4d 100644 --- a/src/resolvers/projectResolver.ts +++ b/src/resolvers/projectResolver.ts @@ -1255,7 +1255,7 @@ export class ProjectResolver { ); } - await validateProjectWalletAddress(address, projectId, chainType); + await validateProjectWalletAddress(address, projectId, chainType, memo); const adminUser = (await findUserById(project.adminUserId)) as User; await addNewProjectAddress({ @@ -1791,8 +1791,12 @@ export class ProjectResolver { * @returns */ @Query(_returns => Boolean) - async walletAddressIsValid(@Arg('address') address: string) { - return validateProjectWalletAddress(address); + async walletAddressIsValid( + @Arg('address') address: string, + @Arg('chainType', { nullable: true }) chainType?: ChainType, + @Arg('memo', { nullable: true }) memo?: string, + ) { + return validateProjectWalletAddress(address, undefined, chainType, memo); } /** diff --git a/src/utils/validators/projectValidator.ts b/src/utils/validators/projectValidator.ts index 27fec6e16..508183613 100644 --- a/src/utils/validators/projectValidator.ts +++ b/src/utils/validators/projectValidator.ts @@ -23,6 +23,7 @@ export const validateProjectWalletAddress = async ( walletAddress: string, projectId?: number, chainType?: ChainType, + memo?: string, ): Promise => { if (!isWalletAddressValid(walletAddress, chainType)) { throw new Error( @@ -40,11 +41,18 @@ export const validateProjectWalletAddress = async ( const relatedAddress = await findRelatedAddressByWalletAddress( walletAddress, chainType, + memo, ); if (relatedAddress && relatedAddress?.project?.id !== projectId) { - throw new Error( - `Address ${walletAddress} is already being used for a project`, - ); + if (chainType === ChainType.STELLAR && memo) { + throw new Error( + `Address ${walletAddress} is already being used for a project with the same MEMO. Please enter a different address or a different MEMO`, + ); + } else { + throw new Error( + `Address ${walletAddress} is already being used for a project`, + ); + } } return true; };