Skip to content
Open
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
2 changes: 1 addition & 1 deletion v2/api-validator/src/client/generated/ApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class ApiClient {
constructor(config?: Partial<OpenAPIConfig>, HttpRequest: HttpRequestConstructor = AxiosHttpRequest) {
this.request = new HttpRequest({
BASE: config?.BASE ?? 'http://0.0.0.0:8000',
VERSION: config?.VERSION ?? '0.5.0',
VERSION: config?.VERSION ?? '0.5.1',
WITH_CREDENTIALS: config?.WITH_CREDENTIALS ?? false,
CREDENTIALS: config?.CREDENTIALS ?? 'include',
TOKEN: config?.TOKEN,
Expand Down
2 changes: 1 addition & 1 deletion v2/api-validator/src/client/generated/core/OpenAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export type OpenAPIConfig = {

export const OpenAPI: OpenAPIConfig = {
BASE: 'http://0.0.0.0:8000',
VERSION: '0.5.0',
VERSION: '0.5.1',
WITH_CREDENTIALS: false,
CREDENTIALS: 'include',
TOKEN: undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@

import type { PublicBlockchainTransactionDestination } from './PublicBlockchainTransactionDestination';

/**
* Blockchain transaction details. When the withdrawal or deposit has succeeded, `blockchainTxId` (transaction hash) is required and must be non-empty, although it is indicated as optional in the schema.
*/
export type PublicBlockchainTransaction = (PublicBlockchainTransactionDestination & {
/**
* Transaction hash on the blockchain. Required (non-empty) when the transaction has succeeded.
*/
blockchainTxId?: string;
});

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class TransfersBlockchainService {

/**
* Get list of withdrawals over public blockchains sorted by creation time
* Retrieves a paginated list of withdrawal transactions sent over public blockchains. Includes cryptocurrency transfers to external blockchain addresses, sorted by creation time.
* Retrieves a paginated list of withdrawal transactions sent over public blockchains. Includes cryptocurrency transfers to external blockchain addresses, sorted by creation time. On success (status succeeded), the destination `blockchainTxId` (transaction hash) is required and must be non-empty.
*
* @returns any List of withdrawals.
* @throws ApiError
Expand Down Expand Up @@ -99,7 +99,8 @@ export class TransfersBlockchainService {

/**
* Create new withdrawal over public blockchain
* Should reject any non blockchain withdrawal request.
* Should reject any non blockchain withdrawal request. In the response, when the transaction has succeeded, `blockchainTxId` (transaction hash) on the destination is required and must be non-empty.
*
* @returns BlockchainWithdrawal New withdrawal has been successfully created.
* @throws ApiError
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class TransfersFiatService {
/**
* Get list of fiat withdrawals sorted by creation time
* Retrieves a paginated list of fiat currency withdrawal transactions. Includes traditional banking transfers and wire transfers, sorted by creation time.
* The `referenceId` on the destination is expected to become mandatory (non-empty) when the transaction is in a finalized state (succeeded or failed), although it is optional in the schema.
*
* @returns any List of withdrawals.
* @throws ApiError
Expand Down Expand Up @@ -99,7 +100,8 @@ export class TransfersFiatService {

/**
* Create new fiat withdrawal
* Should reject any non fiat withdrawal request.
* Should reject any non fiat withdrawal request. In the response, `referenceId` on the destination is expected to become mandatory (non-empty) when the transaction reaches a finalized state (succeeded or failed), although optional in the schema.
*
* @returns FiatWithdrawal New withdrawal has been successfully created.
* @throws ApiError
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export class TransfersPeerAccountsService {
/**
* Get list of withdrawals to peer accounts, sorted by creation time
* Retrieves a paginated list of withdrawal transactions sent to peer accounts. Includes transfers to other accounts within the same provider ecosystem, sorted by creation time.
* The `referenceId` on the destination is expected to become mandatory (non-empty) when the transaction is in a finalized state (succeeded or failed), although it is optional in the schema.
*
* @returns any List of withdrawals.
* @throws ApiError
Expand Down Expand Up @@ -97,7 +98,8 @@ export class TransfersPeerAccountsService {

/**
* Create new withdrawal to a peer account
* Should reject any non peer acount withdrawal request.
* Should reject any non peer acount withdrawal request. In the response, `referenceId` on the destination is expected to become mandatory (non-empty) when the transaction reaches a finalized state (succeeded or failed), although optional in the schema.
*
* @returns PeerAccountWithdrawal New withdrawal has been successfully created.
* @throws ApiError
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export class TransfersService {
/**
* Get list of withdrawals sorted by creation time
* Retrieves a paginated list of all withdrawal transactions for the specified account. Withdrawals are sorted by creation time and include all types of withdrawal operations.
* If a withdrawal is in a terminal state (succeeded or failed), and it has an optional destination.referenceId property (not all transfer methods have this property), then this property MUST contain a valid value, despite it being defined as optional in the schema.
* Blockchain withdrawals in status succeeded, MUST contain destination.blockchainTxId property with a valid value (e.g., transaction hash), despite it being defined as optional in the schema.
*
* @returns any List of withdrawals.
* @throws ApiError
Expand Down Expand Up @@ -98,6 +100,7 @@ export class TransfersService {
/**
* Get withdrawal details
* Retrieves detailed information about a specific withdrawal transaction, including status, amounts, fees, destination details, and processing information.
* For Peer, Fiat and other transfer methods that define a `referenceId` on the destination, this property is expected to become mandatory (non-empty) when the transaction is in a finalized state (succeeded or failed), although it is currently indicated as optional in the schema. For blockchain withdrawals, when the transaction has succeeded, `blockchainTxId` (transaction hash) on the destination is required and must be non-empty.
*
* @returns Withdrawal Withdrawals details.
* @throws ApiError
Expand Down Expand Up @@ -159,6 +162,7 @@ export class TransfersService {
/**
* Get list of deposits sorted by creation time in a descending order
* Retrieves a paginated list of all deposit transactions for the specified account. Deposits are sorted by creation time in descending order and include all types of deposit operations.
* For Peer, Fiat and other transfer methods that define a `referenceId` on the source, this property is expected to become mandatory (non-empty) when the transaction is in a finalized state (succeeded or failed), although it is currently indicated as optional in the schema. For deposits with a blockchain source, when the transaction has succeeded, `blockchainTxId` (transaction hash) on the source is required and must be non-empty.
*
* @returns any Deposits details.
* @throws ApiError
Expand Down Expand Up @@ -236,6 +240,7 @@ export class TransfersService {
/**
* Get deposit details
* Retrieves detailed information about a specific deposit transaction, including status, amounts, source details, confirmation information, and processing details.
* For Peer, Fiat and other transfer methods that define a `referenceId` on the source, this property is expected to become mandatory (non-empty) when the transaction is in a finalized state (succeeded or failed), although it is currently indicated as optional in the schema. For deposits with a blockchain source, when the transaction has succeeded, `blockchainTxId` (transaction hash) on the source is required and must be non-empty.
*
* @returns Deposit List of deposits.
* @throws ApiError
Expand Down
41 changes: 41 additions & 0 deletions v2/api-validator/src/server/controllers/deposit-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import {
DepositAddressStatus,
DepositCapability,
DepositDestination,
DepositStatus,
IbanCapability,
InternalTransferMethod,
PublicBlockchainCapability,
} from '../../client/generated';
import { Repository } from './repository';
Expand Down Expand Up @@ -73,6 +75,8 @@ export class DepositController {
const knownAssetIds = AssetsController.getAllAdditionalAssets().map((a) => a.id);

injectKnownAssetIdsToDeposits(knownAssetIds, this.depositRepository);
ensureReferenceIdForFinalizedDeposits(this.depositRepository);
ensureBlockchainTxIdForSucceededDeposits(this.depositRepository);
injectKnownAssetIdsToDepositAddresses(knownAssetIds, this.depositAddressRepository);
injectKnownAssetIdsToDepositCapabilities(knownAssetIds, this.depositCapabilitiesRepository);
this.depositCapabilitiesRepository.removeDuplicatesBy((dc) => dc.deposit);
Expand Down Expand Up @@ -216,6 +220,43 @@ function injectKnownAssetIdsToDeposits(
}
}

function ensureReferenceIdForFinalizedDeposits(depositRepository: Repository<Deposit>): void {
const finalized = [DepositStatus.SUCCEEDED, DepositStatus.FAILED];
for (const { id } of depositRepository.list()) {
const deposit = depositRepository.find(id);
if (!deposit || !finalized.includes(deposit.status)) continue;
const tm = deposit.source.transferMethod;
if (
tm === PublicBlockchainCapability.transferMethod.PUBLIC_BLOCKCHAIN ||
tm === InternalTransferMethod.transferMethod.INTERNAL_TRANSFER
) {
continue;
}
const source = deposit.source as { referenceId?: string };
if (!source.referenceId || source.referenceId.trim() === '') {
source.referenceId = randomUUID();
}
}
}

function ensureBlockchainTxIdForSucceededDeposits(depositRepository: Repository<Deposit>): void {
for (const { id } of depositRepository.list()) {
const deposit = depositRepository.find(id);
if (
!deposit ||
deposit.status !== DepositStatus.SUCCEEDED ||
deposit.source.transferMethod !==
PublicBlockchainCapability.transferMethod.PUBLIC_BLOCKCHAIN
) {
continue;
}
const source = deposit.source as { blockchainTxId?: string };
if (!source.blockchainTxId || source.blockchainTxId.trim() === '') {
source.blockchainTxId = '0x' + randomUUID().replace(/-/g, '').slice(0, 64);
}
}
}

function injectKnownAssetIdsToDepositAddresses(
knownAssetIds: string[],
depositAddressRepository: Repository<DepositAddress>
Expand Down
43 changes: 43 additions & 0 deletions v2/api-validator/src/server/controllers/withdrawal-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ export class WithdrawalController {
const knownAssetIds = AssetsController.getAllAdditionalAssets().map((a) => a.id);

injectKnownAssetIdsToWithdrawals(knownAssetIds, this.withdrawalRepository);
ensureReferenceIdForFinalizedWithdrawals(this.withdrawalRepository);
ensureBlockchainTxIdForSucceededWithdrawals(this.withdrawalRepository);
injectKnownAssetIdsToWithdrawalCapabilities(knownAssetIds, this.withdrawalCapabilityRepository);
this.withdrawalCapabilityRepository.removeDuplicatesBy((dc) => {
return {
Expand Down Expand Up @@ -259,6 +261,47 @@ function injectKnownAssetIdsToWithdrawals(
}
}

function ensureReferenceIdForFinalizedWithdrawals(
withdrawalRepository: Repository<Withdrawal>
): void {
const finalized = [WithdrawalStatus.SUCCEEDED, WithdrawalStatus.FAILED];
for (const { id } of withdrawalRepository.list()) {
const withdrawal = withdrawalRepository.find(id);
if (!withdrawal || !finalized.includes(withdrawal.status)) continue;
const tm = withdrawal.destination.transferMethod;
if (
tm === PublicBlockchainCapability.transferMethod.PUBLIC_BLOCKCHAIN ||
tm === InternalTransferMethod.transferMethod.INTERNAL_TRANSFER
) {
continue;
}
const dest = withdrawal.destination as { referenceId?: string };
if (!dest.referenceId || dest.referenceId.trim() === '') {
dest.referenceId = randomUUID();
}
}
}

function ensureBlockchainTxIdForSucceededWithdrawals(
withdrawalRepository: Repository<Withdrawal>
): void {
for (const { id } of withdrawalRepository.list()) {
const withdrawal = withdrawalRepository.find(id);
if (
!withdrawal ||
withdrawal.status !== WithdrawalStatus.SUCCEEDED ||
withdrawal.destination.transferMethod !==
PublicBlockchainCapability.transferMethod.PUBLIC_BLOCKCHAIN
) {
continue;
}
const dest = withdrawal.destination as { blockchainTxId?: string };
if (!dest.blockchainTxId || dest.blockchainTxId.trim() === '') {
dest.blockchainTxId = '0x' + randomUUID().replace(/-/g, '').slice(0, 64);
}
}
}

function injectKnownAssetIdsToWithdrawalCapabilities(
knownAssetIds: string[],
withdrawalCapabilityRepository: Repository<WithdrawalCapability>
Expand Down
43 changes: 43 additions & 0 deletions v2/api-validator/tests/server-tests/deposits.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
DepositAddressCreationRequest,
DepositAddressStatus,
DepositCapability,
DepositStatus,
IbanCapability,
InternalTransferCapability,
InternalTransferMethod,
Expand Down Expand Up @@ -542,5 +543,47 @@ describe.skipIf(noTransfersCapability)('Deposits', () => {
}
}
});

const isFinalizedDeposit = (d: Deposit) =>
d.status === DepositStatus.SUCCEEDED || d.status === DepositStatus.FAILED;

const depositSourceTransferMethodHasReferenceId = (transferMethod: string) =>
transferMethod !== PublicBlockchainCapability.transferMethod.PUBLIC_BLOCKCHAIN &&
transferMethod !== InternalTransferMethod.transferMethod.INTERNAL_TRANSFER;

it('should return non-empty referenceId for finalized deposits (Get Deposits) when transfer method defines it', () => {
for (const deposits of accountDepositsMap.values()) {
for (const d of deposits.filter(isFinalizedDeposit)) {
if (!depositSourceTransferMethodHasReferenceId(d.source.transferMethod)) continue;
const source = d.source as { referenceId?: string };
expect(
source.referenceId,
`Deposit ${d.id} (${d.source.transferMethod}) in finalized state must have non-empty referenceId`
).toBeDefined();
expect(
typeof source.referenceId === 'string' && source.referenceId.trim().length > 0,
`Deposit ${d.id} source referenceId must be non-empty`
).toBe(true);
}
}
});

it('should return non-empty blockchainTxId for succeeded deposits with blockchain source (Get Deposits)', () => {
for (const deposits of accountDepositsMap.values()) {
for (const d of deposits.filter((x) => x.status === DepositStatus.SUCCEEDED)) {
if (d.source.transferMethod !== PublicBlockchainCapability.transferMethod.PUBLIC_BLOCKCHAIN)
continue;
const source = d.source as { blockchainTxId?: string };
expect(
source.blockchainTxId,
`Succeeded deposit ${d.id} with blockchain source must have non-empty blockchainTxId (transaction hash)`
).toBeDefined();
expect(
typeof source.blockchainTxId === 'string' && source.blockchainTxId.trim().length > 0,
`Deposit ${d.id} source blockchainTxId must be non-empty`
).toBe(true);
}
}
});
});
});
Loading
Loading