Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions migration/1768216257525-MonthlyVolume.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* @typedef {import('typeorm').MigrationInterface} MigrationInterface
* @typedef {import('typeorm').QueryRunner} QueryRunner
*/

/**
* @class
* @implements {MigrationInterface}
*/
module.exports = class MonthlyVolume1768216257525 {
name = 'MonthlyVolume1768216257525'

/**
* @param {QueryRunner} queryRunner
*/
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "deposit_route" ADD "monthlyVolume" float CONSTRAINT "DF_65fa016979e56325f08fcdebd9f" DEFAULT 0`);
await queryRunner.query(`ALTER TABLE "user_data" ADD "monthlyBuyVolume" float NOT NULL CONSTRAINT "DF_69fd224d86005d56d0bfc42e783" DEFAULT 0`);
await queryRunner.query(`ALTER TABLE "user_data" ADD "monthlySellVolume" float NOT NULL CONSTRAINT "DF_8b0a3a1acd4e2a9ec6ac9b22f0a" DEFAULT 0`);
await queryRunner.query(`ALTER TABLE "user_data" ADD "monthlyCryptoVolume" float NOT NULL CONSTRAINT "DF_c2bc7d2a9c5923a33cd61f3cd9d" DEFAULT 0`);
await queryRunner.query(`ALTER TABLE "buy" ADD "monthlyVolume" float NOT NULL CONSTRAINT "DF_ae3f992d494992c7b5bd3c098f2" DEFAULT 0`);
await queryRunner.query(`ALTER TABLE "user" ADD "monthlyBuyVolume" float NOT NULL CONSTRAINT "DF_b71cae68540eac82fa1aff6e553" DEFAULT 0`);
await queryRunner.query(`ALTER TABLE "user" ADD "monthlySellVolume" float NOT NULL CONSTRAINT "DF_9c2e491c0ea4fc32aed0721c830" DEFAULT 0`);
await queryRunner.query(`ALTER TABLE "user" ADD "monthlyCryptoVolume" float NOT NULL CONSTRAINT "DF_57236c7391c6825b830c1ba49ba" DEFAULT 0`);
}

/**
* @param {QueryRunner} queryRunner
*/
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "user" DROP CONSTRAINT "DF_57236c7391c6825b830c1ba49ba"`);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "monthlyCryptoVolume"`);
await queryRunner.query(`ALTER TABLE "user" DROP CONSTRAINT "DF_9c2e491c0ea4fc32aed0721c830"`);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "monthlySellVolume"`);
await queryRunner.query(`ALTER TABLE "user" DROP CONSTRAINT "DF_b71cae68540eac82fa1aff6e553"`);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "monthlyBuyVolume"`);
await queryRunner.query(`ALTER TABLE "buy" DROP CONSTRAINT "DF_ae3f992d494992c7b5bd3c098f2"`);
await queryRunner.query(`ALTER TABLE "buy" DROP COLUMN "monthlyVolume"`);
await queryRunner.query(`ALTER TABLE "user_data" DROP CONSTRAINT "DF_c2bc7d2a9c5923a33cd61f3cd9d"`);
await queryRunner.query(`ALTER TABLE "user_data" DROP COLUMN "monthlyCryptoVolume"`);
await queryRunner.query(`ALTER TABLE "user_data" DROP CONSTRAINT "DF_8b0a3a1acd4e2a9ec6ac9b22f0a"`);
await queryRunner.query(`ALTER TABLE "user_data" DROP COLUMN "monthlySellVolume"`);
await queryRunner.query(`ALTER TABLE "user_data" DROP CONSTRAINT "DF_69fd224d86005d56d0bfc42e783"`);
await queryRunner.query(`ALTER TABLE "user_data" DROP COLUMN "monthlyBuyVolume"`);
await queryRunner.query(`ALTER TABLE "deposit_route" DROP CONSTRAINT "DF_65fa016979e56325f08fcdebd9f"`);
await queryRunner.query(`ALTER TABLE "deposit_route" DROP COLUMN "monthlyVolume"`);
}
}
26 changes: 26 additions & 0 deletions migration/1768216892259-BankAddressMax.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* @typedef {import('typeorm').MigrationInterface} MigrationInterface
* @typedef {import('typeorm').QueryRunner} QueryRunner
*/

/**
* @class
* @implements {MigrationInterface}
*/
module.exports = class BankAddressMax1768216892259 {
name = 'BankAddressMax1768216892259'

/**
* @param {QueryRunner} queryRunner
*/
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "bank_account" ALTER COLUMN "bankAddress" nvarchar(MAX)`);
}

/**
* @param {QueryRunner} queryRunner
*/
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "bank_account" ALTER COLUMN "bankAddress" nvarchar(256)`);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,18 @@ export abstract class FrankencoinBasedService {
}
}

async getCoinGeckoPrice(contractaddress: string): Promise<number | undefined> {
async getCoinGeckoPrice(contractAddress: string): Promise<number | undefined> {
try {
const price = await this.pricingService.getPriceFrom(
PriceSource.COIN_GECKO,
contractaddress.toLowerCase(),
contractAddress.toLowerCase(),
'usd',
'contract',
);

if (price) return price.price;
} catch (e) {
this.logger.error(`Failed to get CoinGecko price for collateral ${contractaddress}:`, e);
this.logger.error(`Failed to get CoinGecko price for collateral ${contractAddress}:`, e);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/shared/utils/__tests__/async-field.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ describe('AsyncField', () => {

try {
await field;
} catch (e) {
} catch (_e) {
// Expected
}

Expand Down
10 changes: 10 additions & 0 deletions src/shared/utils/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,16 @@ export class Util {
return this.daysAfter(-days, from);
}

static startOfMonth(from?: Date): Date {
const date = from ?? new Date();
return new Date(date.getFullYear(), date.getMonth(), 1);
}

static startOfYear(from?: Date): Date {
const date = from ?? new Date();
return new Date(date.getFullYear(), 0, 1);
}

static sameDay(a: Date, b: Date): boolean {
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
}
Expand Down
7 changes: 4 additions & 3 deletions src/subdomains/core/aml/services/aml.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,10 @@ export class AmlService {
if (entity instanceof BuyFiat) return this.countryService.getCountryWithSymbol(entity.sell.iban.substring(0, 2));
if (entity.cryptoInput) return undefined;

return this.countryService.getCountryWithSymbol(
entity.checkoutTx?.cardIssuerCountry ?? entity.bankTx.iban.substring(0, 2),
);
const countryCode = entity.checkoutTx?.cardIssuerCountry ?? entity.bankTx?.iban?.substring(0, 2);
if (!countryCode) return undefined;

return this.countryService.getCountryWithSymbol(countryCode);
}

private async getBankData(entity: BuyFiat | BuyCrypto): Promise<BankData | undefined> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,10 @@ export class BuyCryptoPreparationService {
referenceChfPrice,
);

const ibanCountry =
entity.bankTx?.iban || entity.checkoutTx?.cardIssuerCountry
? await this.countryService.getCountryWithSymbol(
entity.bankTx?.iban.substring(0, 2) ?? entity.checkoutTx.cardIssuerCountry,
)
: undefined;
const ibanCountryCode = entity.bankTx?.iban?.substring(0, 2) ?? entity.checkoutTx?.cardIssuerCountry;
const ibanCountry = ibanCountryCode
? await this.countryService.getCountryWithSymbol(ibanCountryCode)
: undefined;

const virtualIban = entity.bankTx?.virtualIban
? await this.virtualIbanService.getByIban(entity.bankTx.virtualIban)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -864,50 +864,56 @@ export class BuyCryptoService {
async updateBuyVolume(buyIds: number[]): Promise<void> {
buyIds = buyIds.filter((u, j) => buyIds.indexOf(u) === j).filter((i) => i); // distinct, not null

for (const id of buyIds) {
const { volume } = await this.buyCryptoRepo
.createQueryBuilder('buyCrypto')
.select('SUM(amountInChf)', 'volume')
.where('buyId = :id', { id: id })
.andWhere('amlCheck = :check', { check: CheckStatus.PASS })
.getRawOne<{ volume: number }>();
const startOfYear = Util.startOfYear();
const startOfMonth = Util.startOfMonth();

const newYear = new Date(new Date().getFullYear(), 0, 1);
const { annualVolume } = await this.buyCryptoRepo
for (const id of buyIds) {
const { volume, annualVolume, monthlyVolume } = await this.buyCryptoRepo
.createQueryBuilder('buyCrypto')
.select('SUM(amountInChf)', 'annualVolume')
.select('SUM(buyCrypto.amountInChf)', 'volume')
.addSelect(
'SUM(CASE WHEN bankTx.bookingDate >= :startOfYear THEN buyCrypto.amountInChf ELSE 0 END)',
'annualVolume',
)
.addSelect(
'SUM(CASE WHEN bankTx.bookingDate >= :startOfMonth THEN buyCrypto.amountInChf ELSE 0 END)',
'monthlyVolume',
)
.leftJoin('buyCrypto.bankTx', 'bankTx')
.where('buyCrypto.buyId = :id', { id: id })
.where('buyCrypto.buyId = :id', { id })
.andWhere('buyCrypto.amlCheck = :check', { check: CheckStatus.PASS })
.andWhere('bankTx.bookingDate >= :year', { year: newYear })
.getRawOne<{ annualVolume: number }>();
.setParameters({ startOfYear, startOfMonth })
.getRawOne<{ volume: number; annualVolume: number; monthlyVolume: number }>();

await this.buyService.updateVolume(id, volume ?? 0, annualVolume ?? 0);
await this.buyService.updateVolume(id, volume ?? 0, annualVolume ?? 0, monthlyVolume ?? 0);
}
}

async updateCryptoRouteVolume(cryptoRouteIds: number[]): Promise<void> {
cryptoRouteIds = cryptoRouteIds.filter((u, j) => cryptoRouteIds.indexOf(u) === j).filter((i) => i); // distinct, not null

for (const id of cryptoRouteIds) {
const { volume } = await this.buyCryptoRepo
.createQueryBuilder('buyCrypto')
.select('SUM(amountInChf)', 'volume')
.where('cryptoRouteId = :id', { id: id })
.andWhere('amlCheck = :check', { check: CheckStatus.PASS })
.getRawOne<{ volume: number }>();
const startOfYear = Util.startOfYear();
const startOfMonth = Util.startOfMonth();

const newYear = new Date(new Date().getFullYear(), 0, 1);
const { annualVolume } = await this.buyCryptoRepo
for (const id of cryptoRouteIds) {
const { volume, annualVolume, monthlyVolume } = await this.buyCryptoRepo
.createQueryBuilder('buyCrypto')
.select('SUM(amountInChf)', 'annualVolume')
.select('SUM(buyCrypto.amountInChf)', 'volume')
.addSelect(
'SUM(CASE WHEN cryptoInput.created >= :startOfYear THEN buyCrypto.amountInChf ELSE 0 END)',
'annualVolume',
)
.addSelect(
'SUM(CASE WHEN cryptoInput.created >= :startOfMonth THEN buyCrypto.amountInChf ELSE 0 END)',
'monthlyVolume',
)
.leftJoin('buyCrypto.cryptoInput', 'cryptoInput')
.where('buyCrypto.cryptoRouteId = :id', { id: id })
.where('buyCrypto.cryptoRouteId = :id', { id })
.andWhere('buyCrypto.amlCheck = :check', { check: CheckStatus.PASS })
.andWhere('cryptoInput.created >= :year', { year: newYear })
.getRawOne<{ annualVolume: number }>();
.setParameters({ startOfYear, startOfMonth })
.getRawOne<{ volume: number; annualVolume: number; monthlyVolume: number }>();

await this.swapService.updateVolume(id, volume ?? 0, annualVolume ?? 0);
await this.swapService.updateVolume(id, volume ?? 0, annualVolume ?? 0, monthlyVolume ?? 0);
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/subdomains/core/buy-crypto/routes/buy/buy.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export class Buy extends IEntity {
@Column({ type: 'float', default: 0 })
annualVolume: number; // CHF

@Column({ type: 'float', default: 0 })
monthlyVolume: number; // CHF

@Column({ default: true })
active: boolean;

Expand Down
21 changes: 17 additions & 4 deletions src/subdomains/core/buy-crypto/routes/buy/buy.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,16 @@ export class BuyService {
await this.buyRepo.update({ annualVolume: Not(0) }, { annualVolume: 0 });
}

async updateVolume(buyId: number, volume: number, annualVolume: number): Promise<void> {
@DfxCron(CronExpression.EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT)
async resetMonthlyVolumes(): Promise<void> {
await this.buyRepo.update({ monthlyVolume: Not(0) }, { monthlyVolume: 0 });
}

async updateVolume(buyId: number, volume: number, annualVolume: number, monthlyVolume: number): Promise<void> {
await this.buyRepo.update(buyId, {
volume: Util.round(volume, Config.defaultVolumeDecimal),
annualVolume: Util.round(annualVolume, Config.defaultVolumeDecimal),
monthlyVolume: Util.round(monthlyVolume, Config.defaultVolumeDecimal),
});

// update user volume
Expand All @@ -76,16 +82,23 @@ export class BuyService {
select: ['id', 'user'],
});
const userVolume = await this.getUserVolume(user.id);
await this.userService.updateBuyVolume(user.id, userVolume.volume, userVolume.annualVolume);

await this.userService.updateBuyVolume(
user.id,
userVolume.volume,
userVolume.annualVolume,
userVolume.monthlyVolume,
);
}

async getUserVolume(userId: number): Promise<{ volume: number; annualVolume: number }> {
async getUserVolume(userId: number): Promise<{ volume: number; annualVolume: number; monthlyVolume: number }> {
return this.buyRepo
.createQueryBuilder('buy')
.select('SUM(volume)', 'volume')
.addSelect('SUM(annualVolume)', 'annualVolume')
.addSelect('SUM(monthlyVolume)', 'monthlyVolume')
.where('userId = :id', { id: userId })
.getRawOne<{ volume: number; annualVolume: number }>();
.getRawOne<{ volume: number; annualVolume: number; monthlyVolume: number }>();
}

async getTotalVolume(): Promise<number> {
Expand Down
3 changes: 3 additions & 0 deletions src/subdomains/core/buy-crypto/routes/swap/swap.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export class Swap extends DepositRoute {
@Column({ type: 'float', default: 0 })
annualVolume: number; // CHF

@Column({ type: 'float', default: 0 })
monthlyVolume: number; // CHF

@ManyToOne(() => User, (user) => user.swaps, { nullable: false })
declare user: User;

Expand Down
21 changes: 17 additions & 4 deletions src/subdomains/core/buy-crypto/routes/swap/swap.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,16 @@ export class SwapService {
await this.swapRepo.update({ annualVolume: Not(0) }, { annualVolume: 0 });
}

async updateVolume(swapId: number, volume: number, annualVolume: number): Promise<void> {
@DfxCron(CronExpression.EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT)
async resetMonthlyVolumes(): Promise<void> {
await this.swapRepo.update({ monthlyVolume: Not(0) }, { monthlyVolume: 0 });
}

async updateVolume(swapId: number, volume: number, annualVolume: number, monthlyVolume: number): Promise<void> {
await this.swapRepo.update(swapId, {
volume: Util.round(volume, Config.defaultVolumeDecimal),
annualVolume: Util.round(annualVolume, Config.defaultVolumeDecimal),
monthlyVolume: Util.round(monthlyVolume, Config.defaultVolumeDecimal),
});

// update user volume
Expand All @@ -103,16 +109,23 @@ export class SwapService {
select: ['id', 'user'],
});
const userVolume = await this.getUserVolume(user.id);
await this.userService.updateCryptoVolume(user.id, userVolume.volume, userVolume.annualVolume);

await this.userService.updateCryptoVolume(
user.id,
userVolume.volume,
userVolume.annualVolume,
userVolume.monthlyVolume,
);
}

async getUserVolume(userId: number): Promise<{ volume: number; annualVolume: number }> {
async getUserVolume(userId: number): Promise<{ volume: number; annualVolume: number; monthlyVolume: number }> {
return this.swapRepo
.createQueryBuilder('crypto')
.select('SUM(volume)', 'volume')
.addSelect('SUM(annualVolume)', 'annualVolume')
.addSelect('SUM(monthlyVolume)', 'monthlyVolume')
.where('userId = :id', { id: userId })
.getRawOne<{ volume: number; annualVolume: number }>();
.getRawOne<{ volume: number; annualVolume: number; monthlyVolume: number }>();
}

async getTotalVolume(): Promise<number> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -481,25 +481,28 @@ export class BuyFiatService {
async updateSellVolume(sellIds: number[]): Promise<void> {
sellIds = sellIds.filter((u, j) => sellIds.indexOf(u) === j).filter((i) => i); // distinct, not null

for (const id of sellIds) {
const { volume } = await this.buyFiatRepo
.createQueryBuilder('buyFiat')
.select('SUM(amountInChf)', 'volume')
.where('sellId = :id', { id: id })
.andWhere('amlCheck = :check', { check: CheckStatus.PASS })
.getRawOne<{ volume: number }>();
const startOfYear = Util.startOfYear();
const startOfMonth = Util.startOfMonth();

const newYear = new Date(new Date().getFullYear(), 0, 1);
const { annualVolume } = await this.buyFiatRepo
for (const id of sellIds) {
const { volume, annualVolume, monthlyVolume } = await this.buyFiatRepo
.createQueryBuilder('buyFiat')
.select('SUM(amountInChf)', 'annualVolume')
.select('SUM(buyFiat.amountInChf)', 'volume')
.addSelect(
'SUM(CASE WHEN cryptoInput.created >= :startOfYear THEN buyFiat.amountInChf ELSE 0 END)',
'annualVolume',
)
.addSelect(
'SUM(CASE WHEN cryptoInput.created >= :startOfMonth THEN buyFiat.amountInChf ELSE 0 END)',
'monthlyVolume',
)
.leftJoin('buyFiat.cryptoInput', 'cryptoInput')
.where('buyFiat.sellId = :id', { id: id })
.where('buyFiat.sellId = :id', { id })
.andWhere('buyFiat.amlCheck = :check', { check: CheckStatus.PASS })
.andWhere('cryptoInput.created >= :year', { year: newYear })
.getRawOne<{ annualVolume: number }>();
.setParameters({ startOfYear, startOfMonth })
.getRawOne<{ volume: number; annualVolume: number; monthlyVolume: number }>();

await this.sellService.updateVolume(id, volume ?? 0, annualVolume ?? 0);
await this.sellService.updateVolume(id, volume ?? 0, annualVolume ?? 0, monthlyVolume ?? 0);
}
}

Expand Down
Loading
Loading