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
15 changes: 15 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as bodyParser from 'body-parser';
import * as fs from 'fs';
import * as path from 'path';
import { AppModule } from './app.module';
import { RoyaltyConfigurationService } from './nft/royalty-configuration.service';
import { PayoutsService } from './payouts/payouts.service';
import { StellarWebhookService } from './subscriptions/stellar-webhook.service';
import { MetricsInterceptor } from './metrics/metrics.interceptor';
Expand Down Expand Up @@ -52,6 +53,20 @@ async function bootstrap() {
process.exit(1);
}

// Validate royalty configuration on startup
const royaltyConfigService = app.get(RoyaltyConfigurationService);
try {
royaltyConfigService.validateRoyaltyConfiguration();
logger.log(
`Royalty configuration validated: ` +
`creatorRoyaltyBps=${royaltyConfigService.getCreatorRoyaltyBps()}, ` +
`platformRoyaltyBps=${royaltyConfigService.getPlatformRoyaltyBps()}`,
);
} catch (error) {
logger.error(`Invalid royalty configuration: ${error.message}`);
process.exit(1);
}

// Swagger setup - only available in non-production environments
const swaggerConfig = new DocumentBuilder()
.setTitle('ClipCash API')
Expand Down
69 changes: 65 additions & 4 deletions src/nft/royalty-configuration.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import StellarSdk from '@stellar/stellar-sdk';
import { ConfigService } from '../config/config.service';
import { CLIP_ROYALTY_BPS_MAX } from '../common/validators/is-valid-royalty-bps.validator';
import {
CLIP_ROYALTY_BPS_MAX,
DEFAULT_ROYALTY_BPS_MAX,
} from '../common/validators/is-valid-royalty-bps.validator';

export const ROYALTY_PROTOCOL_MAX_BPS = DEFAULT_ROYALTY_BPS_MAX;

export interface RoyaltyMapEntry {
key: unknown;
Expand Down Expand Up @@ -42,12 +47,70 @@ export class RoyaltyConfigurationService {
}
}

validatePlatformRoyaltyBps(bps: number): void {
if (!Number.isInteger(bps) || isNaN(bps) || bps < 0) {
throw new BadRequestException(
`Invalid platformRoyaltyBps: ${bps}. Must be a non-negative integer.`,
);
}
}

validateCombinedRoyaltyBps(creatorBps: number, platformBps: number): void {
const total = creatorBps + platformBps;
if (total > ROYALTY_PROTOCOL_MAX_BPS) {
throw new BadRequestException(
`Combined royalty (${total} bps = ${creatorBps} creator + ${platformBps} platform) exceeds protocol maximum of ${ROYALTY_PROTOCOL_MAX_BPS} bps.`,
);
}
}

/**
* Full startup validation of royalty environment configuration.
* Throws Error (not BadRequestException) so bootstrap can catch it and exit.
*/
validateRoyaltyConfiguration(): void {
const platformWallet = this.config.platformWallet;
if (!platformWallet) {
throw new Error('PLATFORM_WALLET_ADDRESS must be configured');
}
if (!StellarSdk.StrKey.isValidEd25519PublicKey(platformWallet)) {
throw new Error(
`PLATFORM_WALLET_ADDRESS "${platformWallet}" is not a valid Stellar Ed25519 public key`,
);
}

const platformBps = this.config.platformRoyaltyBps;
if (!Number.isInteger(platformBps) || isNaN(platformBps) || platformBps < 0) {
throw new Error(
`PLATFORM_ROYALTY_BPS must be a non-negative integer, got: ${platformBps}`,
);
}

const creatorBps = this.config.creatorRoyaltyBps;
if (!Number.isInteger(creatorBps) || isNaN(creatorBps) || creatorBps < 0) {
throw new Error(
`CREATOR_ROYALTY_BPS must be a non-negative integer, got: ${creatorBps}`,
);
}

const total = creatorBps + platformBps;
if (total > ROYALTY_PROTOCOL_MAX_BPS) {
throw new Error(
`Combined royalty (${total} bps = ${creatorBps} creator + ${platformBps} platform) exceeds protocol maximum of ${ROYALTY_PROTOCOL_MAX_BPS} bps`,
);
}
}

buildRoyaltyMap(
creatorWallet: string,
clipRoyaltyBps?: number | null,
): RoyaltyMapEntry[] {
const creatorRoyaltyBps = this.getCreatorRoyaltyBps(clipRoyaltyBps);
const platformWallet = this.getPlatformWallet();
const platformBps = this.getPlatformRoyaltyBps();

this.validatePlatformRoyaltyBps(platformBps);
this.validateCombinedRoyaltyBps(creatorRoyaltyBps, platformBps);

return [
{
Expand All @@ -56,9 +119,7 @@ export class RoyaltyConfigurationService {
},
{
key: StellarSdk.Address.fromString(platformWallet).toScVal(),
value: StellarSdk.nativeToScVal(this.getPlatformRoyaltyBps(), {
type: 'u32',
}),
value: StellarSdk.nativeToScVal(platformBps, { type: 'u32' }),
},
];
}
Expand Down