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
1 change: 1 addition & 0 deletions src/integration/blockchain/icp/dto/icp.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface IcpTransferQueryResult {
transfers: IcpTransfer[];
lastBlockIndex: number;
chainLength: number;
rawTransactionCount: number;
}

// --- Candid query_blocks response types (ICP native ledger) ---
Expand Down
20 changes: 17 additions & 3 deletions src/integration/blockchain/icp/icp-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ export class InternetComputerClient extends BlockchainClient {
lastIndex = start - 1;
}

return { transfers, lastBlockIndex: lastIndex, chainLength };
return { transfers, lastBlockIndex: lastIndex, chainLength, rawTransactionCount: response.blocks.length };
}

private mapBlockToTransfer(block: CandidBlock, index: number): IcpTransfer | undefined {
Expand Down Expand Up @@ -277,9 +277,23 @@ export class InternetComputerClient extends BlockchainClient {
if (transfer) transfers.push(transfer);
}

const lastIndex = response.transactions.length > 0 ? firstIndex + response.transactions.length - 1 : start - 1;
let lastIndex: number;

return { transfers, lastBlockIndex: lastIndex, chainLength: Number(response.log_length) };
if (response.transactions.length > 0) {
lastIndex = firstIndex + response.transactions.length - 1;
} else if (firstIndex > start) {
lastIndex = firstIndex - 1;
this.logger.info(`Skipping archived ICRC blocks ${start}-${lastIndex}, next query starts at ${firstIndex}`);
} else {
lastIndex = start - 1;
}

return {
transfers,
lastBlockIndex: lastIndex,
chainLength: Number(response.log_length),
rawTransactionCount: response.transactions.length,
};
}

private mapIcrcTransaction(tx: CandidIcrcTransaction, index: number, decimals: number): IcpTransfer | undefined {
Expand Down
31 changes: 29 additions & 2 deletions src/subdomains/core/history/mappers/transaction-dto.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ export class TransactionRequestExtended extends TransactionRequest {
export class TransactionDtoMapper {
// BuyCrypto
static mapBuyCryptoTransaction(buyCrypto: BuyCryptoExtended): TransactionDto {
const inputAsset = isAsset(buyCrypto.inputAssetEntity) ? buyCrypto.inputAssetEntity : null;
const outputAsset = buyCrypto.outputAsset;

const dto: TransactionDto = {
id: buyCrypto.transaction.id,
uid: buyCrypto.transaction.uid,
Expand All @@ -54,14 +57,18 @@ export class TransactionDtoMapper {
inputAmount: Util.roundReadable(buyCrypto.inputAmount, amountType(buyCrypto.inputAssetEntity)),
inputAsset: buyCrypto.inputAssetEntity.name,
inputAssetId: buyCrypto.inputAssetEntity.id,
inputChainId: inputAsset?.chainId ?? null,
inputBlockchain: buyCrypto.cryptoInput?.asset.blockchain,
inputEvmChainId: inputAsset?.evmChainId ?? null,
inputPaymentMethod: buyCrypto.paymentMethodIn,
...(buyCrypto.outputAmount ? buyCrypto.exchangeRate : null),
outputAmount:
buyCrypto.outputAmount != null ? Util.roundReadable(buyCrypto.outputAmount, AmountType.ASSET) : null,
outputAsset: buyCrypto.outputAsset?.name,
outputAssetId: buyCrypto.outputAsset?.id,
outputChainId: outputAsset?.chainId ?? null,
outputBlockchain: buyCrypto.outputAsset?.blockchain,
outputEvmChainId: outputAsset?.evmChainId ?? null,
outputPaymentMethod: CryptoPaymentMethod.CRYPTO,
priceSteps: buyCrypto.priceStepsObject,
feeAmount: buyCrypto.totalFeeAmount
Expand All @@ -76,6 +83,7 @@ export class TransactionDtoMapper {
inputTxUrl: buyCrypto?.cryptoInput
? txExplorerUrl(buyCrypto.cryptoInput.asset.blockchain, buyCrypto.cryptoInput.inTxId)
: null,
depositAddress: buyCrypto.cryptoInput?.address?.address ?? null,
outputTxId: buyCrypto.txId,
outputTxUrl: buyCrypto.txId ? txExplorerUrl(buyCrypto.outputAsset?.blockchain, buyCrypto.txId) : null,
outputDate: buyCrypto.outputDate,
Expand Down Expand Up @@ -122,6 +130,8 @@ export class TransactionDtoMapper {

// BuyFiat
static mapBuyFiatTransaction(buyFiat: BuyFiatExtended): TransactionDto {
const inputAsset = isAsset(buyFiat.inputAssetEntity) ? buyFiat.inputAssetEntity : null;

const dto: TransactionDto = {
id: buyFiat.transaction.id,
uid: buyFiat.transaction.uid,
Expand All @@ -131,13 +141,17 @@ export class TransactionDtoMapper {
inputAmount: Util.roundReadable(buyFiat.inputAmount, amountType(buyFiat.inputAssetEntity)),
inputAsset: buyFiat.inputAssetEntity.name,
inputAssetId: buyFiat.inputAssetEntity.id,
inputChainId: inputAsset?.chainId ?? null,
inputBlockchain: buyFiat.cryptoInput?.asset.blockchain,
inputEvmChainId: inputAsset?.evmChainId ?? null,
inputPaymentMethod: CryptoPaymentMethod.CRYPTO,
...(buyFiat.outputAmount ? buyFiat.exchangeRate : null),
outputAmount: buyFiat.outputAmount != null ? Util.roundReadable(buyFiat.outputAmount, AmountType.FIAT) : null,
outputAsset: buyFiat.outputAsset?.name,
outputAssetId: buyFiat.outputAsset?.id,
outputChainId: null,
outputBlockchain: null,
outputEvmChainId: null,
outputPaymentMethod: FiatPaymentMethod.BANK,
outputDate: buyFiat.outputDate,
priceSteps: buyFiat.priceStepsObject,
Expand All @@ -153,6 +167,7 @@ export class TransactionDtoMapper {
inputTxUrl: buyFiat?.cryptoInput
? txExplorerUrl(buyFiat.cryptoInput.asset.blockchain, buyFiat.cryptoInput.inTxId)
: null,
depositAddress: buyFiat.cryptoInput?.address?.address ?? null,
outputTxId: buyFiat.bankTx?.remittanceInfo ?? null,
outputTxUrl: null,
chargebackAmount: buyFiat.chargebackAmount,
Expand Down Expand Up @@ -186,6 +201,8 @@ export class TransactionDtoMapper {
// Waiting TxRequest
static mapTxRequestTransaction(txRequest: TransactionRequestExtended): TransactionDto {
const fees = TransactionDtoMapper.mapFees(txRequest);
const sourceAsset = isAsset(txRequest.sourceAssetEntity) ? txRequest.sourceAssetEntity : null;
const targetAsset = isAsset(txRequest.targetAssetEntity) ? txRequest.targetAssetEntity : null;

const dto: TransactionDto = {
id: null,
Expand All @@ -195,19 +212,24 @@ export class TransactionDtoMapper {
inputAmount: Util.roundReadable(txRequest.amount, amountType(txRequest.sourceAssetEntity)),
inputAsset: txRequest.sourceAssetEntity.name,
inputAssetId: txRequest.sourceAssetEntity.id,
inputBlockchain: isAsset(txRequest.sourceAssetEntity) ? txRequest.sourceAssetEntity.blockchain : null,
inputChainId: sourceAsset?.chainId ?? null,
inputBlockchain: sourceAsset?.blockchain ?? null,
inputEvmChainId: sourceAsset?.evmChainId ?? null,
inputPaymentMethod: txRequest.sourcePaymentMethod,
outputAmount: null,
outputAsset: txRequest.targetAssetEntity?.name,
outputAssetId: txRequest.targetAssetEntity?.id,
outputBlockchain: isAsset(txRequest.targetAssetEntity) ? txRequest.targetAssetEntity?.blockchain : null,
outputChainId: targetAsset?.chainId ?? null,
outputBlockchain: targetAsset?.blockchain ?? null,
outputEvmChainId: targetAsset?.evmChainId ?? null,
outputPaymentMethod: txRequest.targetPaymentMethod,
priceSteps: null,
feeAmount: fees?.total,
feeAsset: fees?.total ? txRequest.sourceAssetEntity.name : null,
fees,
inputTxId: null,
inputTxUrl: null,
depositAddress: null,
outputTxId: null,
outputTxUrl: null,
outputDate: null,
Expand Down Expand Up @@ -247,7 +269,9 @@ export class TransactionDtoMapper {
inputAmount: null,
inputAsset: null,
inputAssetId: null,
inputChainId: null,
inputBlockchain: null,
inputEvmChainId: null,
inputPaymentMethod: null,
exchangeRate: null,
rate: null,
Expand All @@ -257,7 +281,9 @@ export class TransactionDtoMapper {
: null,
outputAsset: refReward.outputAsset.name,
outputAssetId: refReward.outputAsset?.id,
outputChainId: refReward.outputAsset?.chainId ?? null,
outputBlockchain: refReward.targetBlockchain,
outputEvmChainId: refReward.outputAsset?.evmChainId ?? null,
outputPaymentMethod: CryptoPaymentMethod.CRYPTO,
outputDate: refReward.outputDate,
priceSteps: null,
Expand All @@ -266,6 +292,7 @@ export class TransactionDtoMapper {
fees: null,
inputTxId: null,
inputTxUrl: null,
depositAddress: null,
outputTxId: refReward.txId,
outputTxUrl: refReward.txId ? txExplorerUrl(refReward.targetBlockchain, refReward.txId) : null,
chargebackAmount: undefined,
Expand Down
12 changes: 12 additions & 0 deletions src/subdomains/generic/kyc/controllers/kyc.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
KycBeneficialData,
KycChangeAddressData,
KycChangeNameData,
KycChangePhoneData,
KycContactData,
KycFileData,
KycLegalEntityData,
Expand Down Expand Up @@ -266,6 +267,17 @@ export class KycController {
return this.kycService.updateNameChangeData(code, +id, data);
}

@Put('data/phone/:id')
@ApiOkResponse({ type: KycStepBase })
@ApiUnauthorizedResponse(MergedResponse)
async updatePhoneChangeData(
@Headers(CodeHeaderName) code: string,
@Param('id') id: string,
@Body() data: KycChangePhoneData,
): Promise<KycStepBase> {
return this.kycService.updatePhoneChangeData(code, +id, data);
}

@Put('data/confirmation/:id')
@ApiOkResponse({ type: KycStepBase })
@ApiUnauthorizedResponse(MergedResponse)
Expand Down
9 changes: 9 additions & 0 deletions src/subdomains/generic/kyc/dto/input/kyc-data.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,15 @@ export class KycChangeNameData {
lastName: string;
}

export class KycChangePhoneData {
@ApiProperty({ description: 'New phone number' })
@IsNotEmpty()
@IsString()
@Transform(DfxPhoneTransform)
@IsDfxPhone()
phone: string;
}

export class KycPersonalData {
@ApiProperty({ enum: AccountType })
@IsNotEmpty()
Expand Down
2 changes: 1 addition & 1 deletion src/subdomains/generic/kyc/entities/kyc-step.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export class KycStep extends IEntity {
return { url: `${apiUrl}/data/payment/${this.id}`, type: UrlType.API };

case KycStepName.PHONE_CHANGE:
return { url: '', type: UrlType.NONE };
return { url: `${apiUrl}/data/phone/${this.id}`, type: UrlType.API };

case KycStepName.ADDRESS_CHANGE:
return { url: `${apiUrl}/data/address/${this.id}`, type: UrlType.API };
Expand Down
14 changes: 14 additions & 0 deletions src/subdomains/generic/kyc/services/kyc.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import {
KycBeneficialData,
KycChangeAddressData,
KycChangeNameData,
KycChangePhoneData,
KycContactData,
KycFileData,
KycLegalEntityData,
Expand Down Expand Up @@ -772,6 +773,19 @@ export class KycService {
});
}

async updatePhoneChangeData(kycHash: string, stepId: number, data: KycChangePhoneData): Promise<KycStepBase> {
const user = await this.getUser(kycHash);
const kycStep = user.getPendingStepOrThrow(stepId);

await this.userDataService.updatePhone(user, data.phone, false);

await this.kycStepRepo.update(...kycStep.complete({ phone: data.phone }));
await this.createStepLog(user, kycStep);
await this.updateProgress(user, false);

return KycStepMapper.toStepBase(kycStep);
}

async getFinancialData(kycHash: string, ip: string, stepId: number, lang?: string): Promise<KycFinancialOutData> {
const user = await this.getUser(kycHash);
const kycStep = user.getPendingStepOrThrow(stepId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@ export class UserDataService {
}

// --- PHONE UPDATE --- //
async updatePhone(userData: UserData, phone: string): Promise<void> {
async updatePhone(userData: UserData, phone: string, createStep = true): Promise<void> {
if (userData.kycLevel !== KycLevel.LEVEL_0 && !phone)
throw new BadRequestException('KYC already started, user data deletion not allowed');

Expand Down Expand Up @@ -784,10 +784,12 @@ export class UserDataService {
}

// create KYC step
await this.kycService.createCustomKycStep(userData, KycStepName.PHONE_CHANGE, ReviewStatus.COMPLETED, {
phone,
previousPhone,
});
if (createStep) {
await this.kycService.createCustomKycStep(userData, KycStepName.PHONE_CHANGE, ReviewStatus.COMPLETED, {
phone,
previousPhone,
});
}
}

// --- ADDRESS UPDATE --- //
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { ApiProperty } from '@nestjs/swagger';
import { TransactionDetailDto } from 'src/subdomains/supporting/payment/dto/transaction.dto';
import { WebhookDto, WebhookType } from './webhook.dto';

Expand All @@ -21,21 +21,6 @@ export enum PaymentWebhookState {
export class PaymentWebhookData extends TransactionDetailDto {
@ApiProperty()
dfxReference: number;

@ApiPropertyOptional({ description: 'Source token contract address' })
sourceChainId?: string;

@ApiPropertyOptional({ description: 'Destination token contract address' })
destinationChainId?: string;

@ApiPropertyOptional({ description: 'Source EVM chain ID (e.g. 1, 56, 137)' })
sourceEvmChainId?: number;

@ApiPropertyOptional({ description: 'Destination EVM chain ID (e.g. 1, 56, 137)' })
destinationEvmChainId?: number;

@ApiPropertyOptional({ description: 'Deposit address for crypto inputs' })
depositAddress?: string;
}

export class PaymentWebhookDto extends WebhookDto<PaymentWebhookData> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Asset } from 'src/shared/models/asset/asset.entity';
import { CountryDtoMapper } from 'src/shared/models/country/dto/country-dto.mapper';
import {
BuyCryptoExtended,
Expand Down Expand Up @@ -33,57 +32,30 @@ export class WebhookDataMapper {
}

static mapCryptoFiatData(payment: BuyFiatExtended): PaymentWebhookData {
const inputAsset = payment.inputAssetEntity as Asset;

return {
...TransactionDtoMapper.mapBuyFiatTransactionDetail(payment),
dfxReference: payment.id,
sourceChainId: inputAsset.chainId,
destinationChainId: null,
sourceEvmChainId: inputAsset.evmChainId ?? null,
destinationEvmChainId: null,
depositAddress: payment.cryptoInput?.address?.address ?? null,
};
}

static mapFiatFiatData(payment: BuyFiatExtended): PaymentWebhookData {
return {
...TransactionDtoMapper.mapBuyFiatTransactionDetail(payment),
dfxReference: payment.id,
sourceChainId: null,
destinationChainId: null,
sourceEvmChainId: null,
destinationEvmChainId: null,
depositAddress: null,
};
}

static mapCryptoCryptoData(payment: BuyCryptoExtended): PaymentWebhookData {
const inputAsset = payment.inputAssetEntity as Asset;
const outputAsset = payment.outputAsset;

return {
...TransactionDtoMapper.mapBuyCryptoTransactionDetail(payment),
dfxReference: payment.id,
sourceChainId: inputAsset.chainId,
destinationChainId: outputAsset?.chainId ?? null,
sourceEvmChainId: inputAsset.evmChainId ?? null,
destinationEvmChainId: outputAsset?.evmChainId ?? null,
depositAddress: payment.cryptoInput?.address?.address ?? null,
};
}

static mapFiatCryptoData(payment: BuyCryptoExtended): PaymentWebhookData {
const outputAsset = payment.outputAsset;

return {
...TransactionDtoMapper.mapBuyCryptoTransactionDetail(payment),
dfxReference: payment.id,
sourceChainId: null,
destinationChainId: outputAsset?.chainId ?? null,
sourceEvmChainId: null,
destinationEvmChainId: outputAsset?.evmChainId ?? null,
depositAddress: null,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { TronService } from 'src/integration/blockchain/tron/services/tron.servi
import { TatumWebhookService } from 'src/integration/tatum/services/tatum-webhook.service';
import { createCustomAsset } from 'src/shared/models/asset/__mocks__/asset.entity.mock';
import { RepositoryFactory } from 'src/shared/repositories/repository.factory';
import { TransactionRequestService } from 'src/subdomains/supporting/payment/services/transaction-request.service';
import { SettingService } from 'src/shared/models/setting/setting.service';
import { DepositService } from 'src/subdomains/supporting/address-pool/deposit/deposit.service';
import { PayInBitcoinService } from '../../../services/payin-bitcoin.service';
import { PayInInternetComputerService } from '../../../services/payin-icp.service';
import { PayInMoneroService } from '../../../services/payin-monero.service';
Expand Down Expand Up @@ -82,7 +83,7 @@ describe('RegisterStrategyRegistry', () => {
(ConfigModule as Record<string, unknown>).Config = { payment: { internetComputerSeed: 'test' } };
jest.spyOn(InternetComputerUtil, 'createWallet').mockReturnValue({ address: 'test-principal' } as never);
jest.spyOn(InternetComputerUtil, 'accountIdentifier').mockReturnValue('test-account-id');
icpStrategy = new IcpStrategy(mock<PayInInternetComputerService>(), mock<TransactionRequestService>());
icpStrategy = new IcpStrategy(mock<PayInInternetComputerService>(), mock<DepositService>(), mock<SettingService>());

registry = new RegisterStrategyRegistryWrapper(
bitcoinStrategy,
Expand Down
Loading
Loading