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
5 changes: 5 additions & 0 deletions infrastructure/bicep/dfx-api.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ param intWalletAddress string
param stakingWalletAddress string
param utxoSpenderAddress string
param btcCollectorAddress string
param btcOutWalletAddress string

param ethWalletAddress string
@secure()
Expand Down Expand Up @@ -565,6 +566,10 @@ resource apiAppService 'Microsoft.Web/sites@2018-11-01' = {
name: 'BTC_COLLECTOR_ADDRESS'
value: btcCollectorAddress
}
{
name: 'BTC_OUT_WALLET_ADDRESS'
value: btcOutWalletAddress
}
{
name: 'FTP_HOST'
value: '138.201.74.234'
Expand Down
3 changes: 3 additions & 0 deletions infrastructure/bicep/parameters/dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@
"btcCollectorAddress": {
"value": "xxx"
},
"btcOutWalletAddress": {
"value": "xxx"
},
"nodeServicePlanSkuName": {
"value": "P1v2"
},
Expand Down
3 changes: 3 additions & 0 deletions infrastructure/bicep/parameters/loc.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@
"btcCollectorAddress": {
"value": "xxx"
},
"btcOutWalletAddress": {
"value": "xxx"
},
"nodeServicePlanSkuName": {
"value": "B1"
},
Expand Down
3 changes: 3 additions & 0 deletions infrastructure/bicep/parameters/prd.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@
"btcCollectorAddress": {
"value": "xxx"
},
"btcOutWalletAddress": {
"value": "xxx"
},
"nodeServicePlanSkuName": {
"value": "P2v2"
},
Expand Down
61 changes: 61 additions & 0 deletions migration/1666094113435-removeUnusedUserFields.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const { MigrationInterface, QueryRunner } = require("typeorm");

module.exports = class removeUnusedUserFields1666094113435 {
name = 'removeUnusedUserFields1666094113435'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP CONSTRAINT "FK_4aaf6d02199282eb8d3931bff31"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP CONSTRAINT "FK_0b294695467ceecc030f95461c1"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP CONSTRAINT "FK_039c54821427d0adca4db8de366"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP CONSTRAINT "FK_19ab0596b1fab6a44be5491ffb4"`);
await queryRunner.query(`ALTER TABLE "dbo"."user_data" DROP CONSTRAINT "DF_45a8d297955d5c3896ea84afa4b"`);
await queryRunner.query(`ALTER TABLE "dbo"."user_data" DROP COLUMN "isMigrated"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "mail"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "firstname"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "surname"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "street"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "houseNumber"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "location"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "zip"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "phone"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "countryId"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "languageId"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP CONSTRAINT "DF_625088799076cba7dffb9947942"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "accountType"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "organizationName"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "organizationStreet"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "organizationHouseNumber"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "organizationLocation"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "organizationZip"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "organizationCountryId"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" DROP COLUMN "currencyId"`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "currencyId" int`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "organizationCountryId" int`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "organizationZip" nvarchar(256)`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "organizationLocation" nvarchar(256)`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "organizationHouseNumber" nvarchar(256)`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "organizationStreet" nvarchar(256)`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "organizationName" nvarchar(256)`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "accountType" nvarchar(256) NOT NULL`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD CONSTRAINT "DF_625088799076cba7dffb9947942" DEFAULT 'Personal' FOR "accountType"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "languageId" int`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "countryId" int`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "phone" nvarchar(256)`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "zip" nvarchar(256)`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "location" nvarchar(256)`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "houseNumber" nvarchar(256)`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "street" nvarchar(256)`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "surname" nvarchar(256)`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "firstname" nvarchar(256)`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD "mail" nvarchar(256)`);
await queryRunner.query(`ALTER TABLE "dbo"."user_data" ADD "isMigrated" bit NOT NULL`);
await queryRunner.query(`ALTER TABLE "dbo"."user_data" ADD CONSTRAINT "DF_45a8d297955d5c3896ea84afa4b" DEFAULT 1 FOR "isMigrated"`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD CONSTRAINT "FK_19ab0596b1fab6a44be5491ffb4" FOREIGN KEY ("currencyId") REFERENCES "fiat"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD CONSTRAINT "FK_039c54821427d0adca4db8de366" FOREIGN KEY ("organizationCountryId") REFERENCES "country"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD CONSTRAINT "FK_0b294695467ceecc030f95461c1" FOREIGN KEY ("languageId") REFERENCES "language"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "dbo"."user" ADD CONSTRAINT "FK_4aaf6d02199282eb8d3931bff31" FOREIGN KEY ("countryId") REFERENCES "country"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
}
}
5 changes: 3 additions & 2 deletions src/blockchain/ain/ain.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import { NodeController } from './node/node.controller';
import { NodeService } from './node/node.service';
import { WhaleService } from './whale/whale.service';
import { DeFiChainUtil } from './utils/defichain.util';
import { BtcFeeService } from './services/btc-fee.service';

@Module({
imports: [SharedModule],
providers: [CryptoService, NodeService, WhaleService, DeFiChainUtil],
exports: [CryptoService, NodeService, WhaleService, DeFiChainUtil],
providers: [CryptoService, NodeService, WhaleService, DeFiChainUtil, BtcFeeService],
exports: [CryptoService, NodeService, WhaleService, DeFiChainUtil, BtcFeeService],
controllers: [NodeController],
})
export class AinModule {}
21 changes: 21 additions & 0 deletions src/blockchain/ain/node/btc-client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { SchedulerRegistry } from '@nestjs/schedule';
import { Config } from 'src/config/config';
import { HttpService } from 'src/shared/services/http.service';
import { NodeClient, NodeCommand, NodeMode } from './node-client';

Expand Down Expand Up @@ -27,4 +28,24 @@ export class BtcClient extends NodeClient {
true,
).then((r) => r.txid);
}

async sendMany(payload: { addressTo: string; amount: number }[], feeRate: number): Promise<string> {
const batch = payload.reduce((acc, p) => ({ ...acc, [p.addressTo]: `${p.amount}` }), {});

return await this.callNode<{ txid: string }>(
(c) =>
c.call(
NodeCommand.SEND,
[
batch,
null,
'unset',
null,
{ fee_rate: feeRate, replaceable: true, change_address: Config.blockchain.default.btcOutWalletAddress },
],
'number',
),
true,
).then((r) => r.txid);
}
}
23 changes: 23 additions & 0 deletions src/blockchain/ain/services/btc-fee.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Injectable } from '@nestjs/common';
import { HttpService } from 'src/shared/services/http.service';

@Injectable()
export class BtcFeeService {
private readonly btcFeeUrl = 'https://mempool.space/api/v1/fees/recommended';

constructor(private readonly http: HttpService) {}

async getRecommendedFeeRate(): Promise<number> {
const { fastestFee } = await this.http.get<{
fastestFee: number;
halfHourFee: number;
hourFee: number;
economyFee: number;
minimumFee: number;
}>(this.btcFeeUrl, {
tryCount: 3,
});

return fastestFee;
}
}
16 changes: 14 additions & 2 deletions src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ export class Configuration {
expiresIn: process.env.JWT_EXPIRES_IN ?? 172800,
},
},
company: {
signOptions: {
expiresIn: process.env.JWT_EXPIRES_IN_COMPANY ?? 30,
},
},
challenge: {
expiresIn: +process.env.CHALLENGE_EXPIRES_IN ?? 10,
},
signMessage:
'By_signing_this_message,_you_confirm_that_you_are_the_sole_owner_of_the_provided_DeFiChain_address_and_are_in_possession_of_its_private_key._Your_ID:_',
signMessageWallet:
Expand Down Expand Up @@ -119,8 +127,10 @@ export class Configuration {
apiKey: process.env.FIXER_API_KEY,
};

lock = {
apiKey: process.env.LOCK_API_KEY,
externalKycServices = {
'LOCK.space': {
apiKey: process.env.LOCK_API_KEY,
},
};

mail: MailOptions = {
Expand Down Expand Up @@ -149,6 +159,7 @@ export class Configuration {
contact: {
supportMail: process.env.SUPPORT_MAIL || 'support@dfx.swiss',
monitoringMail: process.env.MONITORING_MAIL || 'monitoring@dfx.swiss',
liqMail: process.env.LIQ_MAIL || 'liq@dfx.swiss',
noReplyMail: process.env.NOREPLY_MAIL || 'noreply@dfx.swiss',
},
};
Expand Down Expand Up @@ -198,6 +209,7 @@ export class Configuration {
intWalletAddress: process.env.INT_WALLET_ADDRESS,
stakingWalletAddress: process.env.STAKING_WALLET_ADDRESS,
btcCollectorAddress: process.env.BTC_COLLECTOR_ADDRESS,
btcOutWalletAddress: process.env.BTC_OUT_WALLET_ADDRESS,
minTxAmount: 0.00000297,
minDeposit: {
Fiat: {
Expand Down
6 changes: 3 additions & 3 deletions src/notification/entities/mail/base/mail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { NotificationType } from 'src/notification/enums';
import { Notification, NotificationOptions, NotificationMetadata } from '../../notification.entity';

export interface MailParams {
to: string;
to: string | string[];
subject: string;
salutation: string;
body: string;
Expand All @@ -26,7 +26,7 @@ export class Mail extends Notification {
name: 'DFX.swiss',
address: GetConfig().mail.contact.noReplyMail,
};
readonly #to: string;
readonly #to: string | string[];
readonly #cc: string;
readonly #bcc: string;
readonly #template: string = GetConfig().mail.defaultMailTemplate;
Expand Down Expand Up @@ -66,7 +66,7 @@ export class Mail extends Notification {
return { name, address };
}

get to(): string {
get to(): string | string[] {
return this.#to;
}

Expand Down
12 changes: 11 additions & 1 deletion src/notification/entities/mail/error-monitoring-mail.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { GetConfig } from 'src/config/config';
import { MailContext } from 'src/notification/enums';
import { NotificationMetadata, NotificationOptions } from '../notification.entity';
import { Mail } from './base/mail';

Expand All @@ -13,8 +14,11 @@ export interface ErrorMonitoringMailParams {

export class ErrorMonitoringMail extends Mail {
constructor(params: ErrorMonitoringMailParams) {
const to = [GetConfig().mail.contact.monitoringMail];
ErrorMonitoringMail.isLiqMail(params) && to.push(GetConfig().mail.contact.liqMail);

const _params = {
to: GetConfig().mail.contact.monitoringMail,
to: to,
subject: `${params.subject} (${GetConfig().environment.toUpperCase()})`,
salutation: 'Hi DFX Tech Support',
body: ErrorMonitoringMail.createBody(params.errors),
Expand All @@ -25,6 +29,12 @@ export class ErrorMonitoringMail extends Mail {
super(_params);
}

private static isLiqMail(params: ErrorMonitoringMailParams): boolean {
return [MailContext.BUY_CRYPTO, MailContext.DEX, MailContext.PAYOUT, MailContext.PRICING].includes(
params.metadata?.context,
);
}

static createBody(errors: string[]): string {
const env = GetConfig().environment.toUpperCase();

Expand Down
1 change: 1 addition & 0 deletions src/notification/services/mail.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface MailOptions {
contact: {
supportMail: string;
monitoringMail: string;
liqMail: string;
noReplyMail: string;
};
}
Expand Down
7 changes: 6 additions & 1 deletion src/payment/models/buy-crypto/entities/buy-crypto.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ export class BuyCrypto extends IEntity {
} else if (this.amlCheck === AmlCheck.PENDING) {
if (this.amlReason === AmlReason.DAILY_LIMIT) return 'mail.payment.pending.dailyLimit';
if (this.amlReason === AmlReason.ANNUAL_LIMIT) return 'mail.payment.pending.annualLimit';
if (this.amlReason === AmlReason.OLKY_NO_KYC) return 'mail.payment.pending.olkyNoKyc';
} else if (this.amlCheck === AmlCheck.FAIL) {
return 'mail.payment.deposit.paybackInitiated';
}
Expand All @@ -224,15 +225,19 @@ export class BuyCrypto extends IEntity {
return this.buy ? this.buy.user : this.cryptoRoute.user;
}

get target(): { address: string; asset: Asset } {
get target(): { address: string; asset: Asset; trimmedReturnAddress: string } {
return this.buy
? {
address: this.buy.deposit?.address ?? this.buy.user.address,
asset: this.buy.asset,
trimmedReturnAddress: this.buy?.iban ? Util.trimIBAN(this.buy.iban) : null,
}
: {
address: this.cryptoRoute.targetDeposit?.address ?? this.cryptoRoute.user.address,
asset: this.cryptoRoute.asset,
trimmedReturnAddress: this.cryptoRoute?.user?.address
? Util.trimBlockchainAddress(this.cryptoRoute.user.address)
: null,
};
}
}
1 change: 1 addition & 0 deletions src/payment/models/buy-crypto/enums/aml-reason.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export enum AmlReason {
KYC_REJECTED = 'KycRejected',
NAME_CHECK = 'NameCheck',
OLKY_NO_KYC = 'OlkyNoKyc',
MIN_DEPOSIT_NOT_REACHED = 'MinDepositNotReached',
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { BlockchainExplorerUrls } from 'src/blockchain/shared/enums/blockchain.e
import { AmlCheck } from '../enums/aml-check.enum';
import { I18nService } from 'nestjs-i18n';
import { AmlReason } from '../enums/aml-reason.enum';
import { Config } from 'src/config/config';

@Injectable()
export class BuyCryptoNotificationService {
Expand All @@ -32,6 +33,7 @@ export class BuyCryptoNotificationService {
txId: Not(IsNull()),
isComplete: true,
batch: { status: BuyCryptoBatchStatus.COMPLETE },
amlCheck: AmlCheck.PASS,
},
relations: [
'bankTx',
Expand Down Expand Up @@ -137,7 +139,7 @@ export class BuyCryptoNotificationService {
returnReason: await this.i18nService.translate(`mail.amlReasonMailText.${entity.amlReason}`, {
lang: entity.user.userData.language?.symbol.toLowerCase(),
}),
userAddressTrimmed: Util.trimBlockchainAddress(entity.user.address),
userAddressTrimmed: entity.target.trimmedReturnAddress,
},
},
});
Expand All @@ -157,7 +159,7 @@ export class BuyCryptoNotificationService {
outputAmount: IsNull(),
chargebackDate: IsNull(),
chargebackBankTx: IsNull(),
amlReason: In([AmlReason.DAILY_LIMIT, AmlReason.ANNUAL_LIMIT]),
amlReason: In([AmlReason.DAILY_LIMIT, AmlReason.ANNUAL_LIMIT, AmlReason.OLKY_NO_KYC]),
amlCheck: AmlCheck.PENDING,
},
relations: [
Expand All @@ -182,7 +184,7 @@ export class BuyCryptoNotificationService {
userData: entity.user.userData,
translationKey: entity.translationKey,
translationParams: {
hashLink: `https://payment.dfx.swiss/kyc?code=${entity.user.userData.kycHash}`,
hashLink: `${Config.payment.url}/kyc?code=${entity.user.userData.kycHash}`,
},
},
});
Expand Down
Loading