refactor confirmation tiers to shared crypto-agnostic types and validation
This commit is contained in:
@@ -12,7 +12,7 @@ import {
|
|||||||
} from '../types/Config';
|
} from '../types/Config';
|
||||||
import { MoneroNetwork } from '../types/MoneroNetwork';
|
import { MoneroNetwork } from '../types/MoneroNetwork';
|
||||||
import { MoneroWalletConfig } from '../types/MoneroWalletConfig';
|
import { MoneroWalletConfig } from '../types/MoneroWalletConfig';
|
||||||
import { MoneroConfirmationTier } from '../types/MoneroConfirmationTier';
|
import { ConfirmationTier } from '../types/ConfirmationTier';
|
||||||
import { NodeEnv } from '../types/NodeEnv';
|
import { NodeEnv } from '../types/NodeEnv';
|
||||||
import { ShopFiatCurrency } from '../types/ShopFiatCurrency';
|
import { ShopFiatCurrency } from '../types/ShopFiatCurrency';
|
||||||
import { PaymentMethod } from '../modules/payment/types/PaymentMethod';
|
import { PaymentMethod } from '../modules/payment/types/PaymentMethod';
|
||||||
@@ -178,7 +178,7 @@ export const getShopSettingsConfig = (): ShopSettingsConfig => ({
|
|||||||
shopName: env('SHOP_NAME'),
|
shopName: env('SHOP_NAME'),
|
||||||
shopFiatCurrency: env('SHOP_FIAT_CURRENCY') as ShopFiatCurrency,
|
shopFiatCurrency: env('SHOP_FIAT_CURRENCY') as ShopFiatCurrency,
|
||||||
monero: {
|
monero: {
|
||||||
confirmationTiers: JSON.parse(env('MONERO_CONFIRMATION_TIERS')) as MoneroConfirmationTier[]
|
confirmationTiers: JSON.parse(env('MONERO_CONFIRMATION_TIERS')) as ConfirmationTier[]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { IsBoolean, IsEnum, IsNotEmpty, IsNumber, IsString, Max, Min, validateSy
|
|||||||
import { NodeEnv } from '../types/NodeEnv';
|
import { NodeEnv } from '../types/NodeEnv';
|
||||||
import { ShopFiatCurrency } from '../types/ShopFiatCurrency';
|
import { ShopFiatCurrency } from '../types/ShopFiatCurrency';
|
||||||
import { IsBase64 } from '../validation/decorators/isBase64';
|
import { IsBase64 } from '../validation/decorators/isBase64';
|
||||||
import { IsMoneroConfirmationTiers } from '../validation/decorators/isMoneroConfirmationTiers';
|
import { IsConfirmationTiers } from '../validation/decorators/isConfirmationTiers';
|
||||||
import { MoneroNetwork } from '../types/MoneroNetwork';
|
import { MoneroNetwork } from '../types/MoneroNetwork';
|
||||||
|
|
||||||
class EnvironmentVariables {
|
class EnvironmentVariables {
|
||||||
@@ -265,7 +265,7 @@ class EnvironmentVariables {
|
|||||||
|
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsMoneroConfirmationTiers()
|
@IsConfirmationTiers()
|
||||||
MONERO_CONFIRMATION_TIERS: string;
|
MONERO_CONFIRMATION_TIERS: string;
|
||||||
|
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { Config } from '../../../types/Config';
|
|||||||
import { getErrorMessage } from '../../../utils/getErrorMessage';
|
import { getErrorMessage } from '../../../utils/getErrorMessage';
|
||||||
import { convertFiatToXmr } from '../../../utils/monero/convertFiatToXmr';
|
import { convertFiatToXmr } from '../../../utils/monero/convertFiatToXmr';
|
||||||
import { convertXmrToXmrAtomic } from '../../../utils/monero/convertXmrToXmrAtomic';
|
import { convertXmrToXmrAtomic } from '../../../utils/monero/convertXmrToXmrAtomic';
|
||||||
import { resolveMinConfirmations } from '../../../utils/monero/resolveMinConfirmations';
|
import { resolveMinConfirmations } from '../../../utils/confirmation/resolveMinConfirmations';
|
||||||
import { MoneroWalletRpcClient } from '../../moneroWallet/services/MoneroWalletRpcClient';
|
import { MoneroWalletRpcClient } from '../../moneroWallet/services/MoneroWalletRpcClient';
|
||||||
import { ExchangeRateService } from '../../exchangeRate/services/ExchangeRateService';
|
import { ExchangeRateService } from '../../exchangeRate/services/ExchangeRateService';
|
||||||
import { Invoice } from '../entities/Invoice';
|
import { Invoice } from '../entities/Invoice';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { MoneroConfirmationTier } from '../../../types/MoneroConfirmationTier';
|
import { ConfirmationTier } from '../../../types/ConfirmationTier';
|
||||||
|
|
||||||
export interface ShopSettingsMoneroView {
|
export interface ShopSettingsMoneroView {
|
||||||
confirmationTiers: MoneroConfirmationTier[];
|
confirmationTiers: ConfirmationTier[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
export interface BitcoinConfirmationTier {
|
|
||||||
upToTotalFiat?: string;
|
|
||||||
minConfirmations: number;
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { MoneroConfirmationTier } from './MoneroConfirmationTier';
|
import { ConfirmationTier } from './ConfirmationTier';
|
||||||
import { MoneroWalletConfig } from './MoneroWalletConfig';
|
import { MoneroWalletConfig } from './MoneroWalletConfig';
|
||||||
import { NodeEnv } from './NodeEnv';
|
import { NodeEnv } from './NodeEnv';
|
||||||
import { PaymentMethod } from '../modules/payment/types/PaymentMethod';
|
import { PaymentMethod } from '../modules/payment/types/PaymentMethod';
|
||||||
@@ -55,7 +55,7 @@ export interface ShopSettingsConfig {
|
|||||||
shopName: string;
|
shopName: string;
|
||||||
shopFiatCurrency: ShopFiatCurrency;
|
shopFiatCurrency: ShopFiatCurrency;
|
||||||
monero: {
|
monero: {
|
||||||
confirmationTiers: MoneroConfirmationTier[];
|
confirmationTiers: ConfirmationTier[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export interface MoneroConfirmationTier {
|
export interface ConfirmationTier {
|
||||||
upToTotalFiat?: string;
|
upToTotalFiat?: string;
|
||||||
minConfirmations: number;
|
minConfirmations: number;
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import Decimal from 'decimal.js';
|
import Decimal from 'decimal.js';
|
||||||
import type { MoneroConfirmationTier } from '../../types/MoneroConfirmationTier';
|
import type { ConfirmationTier } from '../../types/ConfirmationTier';
|
||||||
|
|
||||||
export const resolveMinConfirmations = (totalFiat: number, tiers: MoneroConfirmationTier[]): number => {
|
export const resolveMinConfirmations = (totalFiat: number, tiers: ConfirmationTier[]): number => {
|
||||||
for (const tier of tiers) {
|
for (const tier of tiers) {
|
||||||
if (tier.upToTotalFiat === undefined) {
|
if (tier.upToTotalFiat === undefined) {
|
||||||
return tier.minConfirmations;
|
return tier.minConfirmations;
|
||||||
+5
-5
@@ -1,13 +1,13 @@
|
|||||||
import { validateSync } from 'class-validator';
|
import { validateSync } from 'class-validator';
|
||||||
import { IsMoneroConfirmationTiers } from './isMoneroConfirmationTiers';
|
import { IsConfirmationTiers } from './isConfirmationTiers';
|
||||||
|
|
||||||
class TestDto {
|
class TestDto {
|
||||||
@IsMoneroConfirmationTiers()
|
@IsConfirmationTiers()
|
||||||
MONERO_CONFIRMATION_TIERS: string;
|
CONFIRMATION_TIERS: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateTiers = (value: string) => {
|
const validateTiers = (value: string) => {
|
||||||
const dto = Object.assign(new TestDto(), { MONERO_CONFIRMATION_TIERS: value });
|
const dto = Object.assign(new TestDto(), { CONFIRMATION_TIERS: value });
|
||||||
|
|
||||||
return validateSync(dto);
|
return validateSync(dto);
|
||||||
};
|
};
|
||||||
@@ -15,7 +15,7 @@ const validateTiers = (value: string) => {
|
|||||||
const validTiers =
|
const validTiers =
|
||||||
'[{"upToTotalFiat":"25","minConfirmations":0},{"upToTotalFiat":"250","minConfirmations":5},{"minConfirmations":10}]';
|
'[{"upToTotalFiat":"25","minConfirmations":0},{"upToTotalFiat":"250","minConfirmations":5},{"minConfirmations":10}]';
|
||||||
|
|
||||||
describe('IsMoneroConfirmationTiers', () => {
|
describe('IsConfirmationTiers', () => {
|
||||||
it('accepts valid default tiers', () => {
|
it('accepts valid default tiers', () => {
|
||||||
expect(validateTiers(validTiers)).toHaveLength(0);
|
expect(validateTiers(validTiers)).toHaveLength(0);
|
||||||
});
|
});
|
||||||
+10
-10
@@ -1,5 +1,5 @@
|
|||||||
import { Validate, ValidatorConstraint, type ValidatorConstraintInterface } from 'class-validator';
|
import { Validate, ValidatorConstraint, type ValidatorConstraintInterface } from 'class-validator';
|
||||||
import type { MoneroConfirmationTier } from '../../types/MoneroConfirmationTier';
|
import type { ConfirmationTier } from '../../types/ConfirmationTier';
|
||||||
|
|
||||||
const isPositiveDecimalString = (value: string): boolean => {
|
const isPositiveDecimalString = (value: string): boolean => {
|
||||||
const trimmed = value.trim();
|
const trimmed = value.trim();
|
||||||
@@ -16,12 +16,12 @@ const isPositiveDecimalString = (value: string): boolean => {
|
|||||||
const isMinConfirmations = (value: unknown): boolean =>
|
const isMinConfirmations = (value: unknown): boolean =>
|
||||||
typeof value === 'number' && Number.isInteger(value) && value >= 0;
|
typeof value === 'number' && Number.isInteger(value) && value >= 0;
|
||||||
|
|
||||||
const isMoneroConfirmationTier = (value: unknown): value is MoneroConfirmationTier => {
|
const isConfirmationTier = (value: unknown): value is ConfirmationTier => {
|
||||||
if (typeof value !== 'object' || value === null) {
|
if (typeof value !== 'object' || value === null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tier = value as MoneroConfirmationTier;
|
const tier = value as ConfirmationTier;
|
||||||
|
|
||||||
if (!isMinConfirmations(tier.minConfirmations)) {
|
if (!isMinConfirmations(tier.minConfirmations)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -34,7 +34,7 @@ const isMoneroConfirmationTier = (value: unknown): value is MoneroConfirmationTi
|
|||||||
return typeof tier.upToTotalFiat === 'string' && isPositiveDecimalString(tier.upToTotalFiat);
|
return typeof tier.upToTotalFiat === 'string' && isPositiveDecimalString(tier.upToTotalFiat);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isValidMoneroConfirmationTiersJson = (raw: string): boolean => {
|
const isValidConfirmationTiersJson = (raw: string): boolean => {
|
||||||
let parsed: unknown;
|
let parsed: unknown;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -43,7 +43,7 @@ const isValidMoneroConfirmationTiersJson = (raw: string): boolean => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Array.isArray(parsed) || parsed.length === 0 || !parsed.every(isMoneroConfirmationTier)) {
|
if (!Array.isArray(parsed) || parsed.length === 0 || !parsed.every(isConfirmationTier)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,19 +75,19 @@ const isValidMoneroConfirmationTiersJson = (raw: string): boolean => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ValidatorConstraint({ name: 'isMoneroConfirmationTiers' })
|
@ValidatorConstraint({ name: 'isConfirmationTiers' })
|
||||||
class IsMoneroConfirmationTiersConstraint implements ValidatorConstraintInterface {
|
class IsConfirmationTiersConstraint implements ValidatorConstraintInterface {
|
||||||
validate(value: unknown): boolean {
|
validate(value: unknown): boolean {
|
||||||
if (typeof value !== 'string' || !value) {
|
if (typeof value !== 'string' || !value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return isValidMoneroConfirmationTiersJson(value);
|
return isValidConfirmationTiersJson(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultMessage(): string {
|
defaultMessage(): string {
|
||||||
return '$property must be a non-empty JSON array of Monero confirmation tiers; minConfirmations must be 0 (tx-detected) or an integer >= 1, 0 may appear only once and not on the catch-all tier, non-final tiers need a positive upToTotalFiat in shop fiat currency, and the last tier must be a catch-all without upToTotalFiat';
|
return '$property must be a non-empty JSON array of confirmation tiers; minConfirmations must be 0 (tx-detected) or an integer >= 1, 0 may appear only once and not on the catch-all tier, non-final tiers need a positive upToTotalFiat in shop fiat currency, and the last tier must be a catch-all without upToTotalFiat';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IsMoneroConfirmationTiers = () => Validate(IsMoneroConfirmationTiersConstraint);
|
export const IsConfirmationTiers = () => Validate(IsConfirmationTiersConstraint);
|
||||||
Reference in New Issue
Block a user