Skip to content

feat(nft): royalty splitting — startup validation, platform bps guard, combined protocol cap#519

Open
petahade wants to merge 1 commit into
ANYTECHS:mainfrom
petahade:feat/royalty-splitting
Open

feat(nft): royalty splitting — startup validation, platform bps guard, combined protocol cap#519
petahade wants to merge 1 commit into
ANYTECHS:mainfrom
petahade:feat/royalty-splitting

Conversation

@petahade

@petahade petahade commented Jun 25, 2026

Copy link
Copy Markdown

Closes #360


Summary

This PR completes the NFT royalty splitting implementation by adding the missing validation layer that ensures both creator and platform royalty entries are always valid before they are encoded into a mint transaction, and that a misconfigured environment fails fast at startup rather than silently at mint time.

The on-chain encoding of both royalty entries (creator + platform) in buildRoyaltyMap() and the mint() Soroban call were already in place. What was missing:

  • No startup check that PLATFORM_WALLET_ADDRESS / PLATFORM_ROYALTY_BPS / CREATOR_ROYALTY_BPS are valid
  • platformRoyaltyBps was read from env and passed directly to nativeToScVal with no validation
  • No guard preventing combined royalties from exceeding the 10 000 bps (100%) protocol cap

Changes

src/nft/royalty-configuration.service.ts

Addition Purpose
ROYALTY_PROTOCOL_MAX_BPS = 10 000 Single source of truth for the protocol cap (re-exported from DEFAULT_ROYALTY_BPS_MAX)
validatePlatformRoyaltyBps(bps) Rejects negative values and non-integers for the platform share
validateCombinedRoyaltyBps(creatorBps, platformBps) Rejects configs where creator + platform > 10 000 bps
validateRoyaltyConfiguration() Full startup validation: non-empty + valid Stellar Ed25519 wallet, both bps values non-negative integers, combined total within cap. Throws plain Error (not BadRequestException) so bootstrap can catch it cleanly.
buildRoyaltyMap() updated Calls validatePlatformRoyaltyBps and validateCombinedRoyaltyBps on every mint, so runtime bad configs are caught at the point of use too.

src/main.ts

Adds a royalty config validation block immediately after the existing BullMQ startup checks:

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

The process exits with code 1 (same as BullMQ config failures) if:

  • PLATFORM_WALLET_ADDRESS is absent or not a valid Stellar Ed25519 public key
  • PLATFORM_ROYALTY_BPS or CREATOR_ROYALTY_BPS is missing, non-integer, or negative
  • The combined total exceeds 10 000 bps

Royalty flow (unchanged, now guarded)

prepareMintTx()
  └── buildRoyaltyMap(creatorWallet, clip.royaltyBps)
        ├── validatePlatformRoyaltyBps(100)        ← NEW guard
        ├── validateCombinedRoyaltyBps(1000, 100)  ← NEW guard (1100 ≤ 10 000 ✓)
        └── returns [
              { key: creatorAddress,  value: 1000 u32 },  // 10% creator
              { key: platformAddress, value: 100 u32  },  // 1% platform
            ]
  └── contract.call('mint', to, token_id, uri, royaltyMap)
        └── on-chain Map<Address, u32> stored by contract

Default configuration verified: CREATOR_ROYALTY_BPS=1000 (10%) + PLATFORM_ROYALTY_BPS=100 (1%) = 1 100 bps total, well within the 10 000 bps protocol cap.


Validation error examples

Scenario Error
Missing PLATFORM_WALLET_ADDRESS PLATFORM_WALLET_ADDRESS must be configured
Invalid wallet format PLATFORM_WALLET_ADDRESS "..." is not a valid Stellar Ed25519 public key
PLATFORM_ROYALTY_BPS=-1 Invalid platformRoyaltyBps: -1. Must be a non-negative integer.
CREATOR_ROYALTY_BPS=9950 + PLATFORM_ROYALTY_BPS=100 Combined royalty (10050 bps = 9950 creator + 100 platform) exceeds protocol maximum of 10000 bps

Backward compatibility

  • buildRoyaltyMap() signature is unchanged
  • validateRoyaltyBps() (creator-only, 0–1500 cap) is unchanged
  • getCreatorRoyaltyBps() / getPlatformRoyaltyBps() / getPlatformWallet() are unchanged
  • All 61 existing NFT/royalty/mint tests pass with no modifications

Extend RoyaltyConfigurationService with platform royalty bps validation,
combined creator+platform total guard against the 10 000 bps protocol cap,
and a validateRoyaltyConfiguration() method used at bootstrap to reject
misconfigured environments (missing/invalid platform wallet, negative or
non-integer bps values, totals that exceed the protocol limit).

buildRoyaltyMap() now enforces these constraints on every mint, so both
the creator (1 000 bps default) and platform (100 bps default) royalty
entries are always encoded, submitted, and persisted correctly on-chain.
@drips-wave

drips-wave Bot commented Jun 25, 2026

Copy link
Copy Markdown

@petahade Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Multiple NFT Royalty Recipients

1 participant