James Bachini

NFT Royalties | New OpenZeppelin Library For Stellar

NFT royalties

In this tutorial we are going to dive into the new NFT token extension for royalties on Stellar.

James On YouTube

NFT royalties provide a mechanism for ensuring that creators continue to benefit from secondary sales of their work. With the latest release of OpenZeppelin’s NFT extensions for Soroban smart contracts on Stellar, developers can now seamlessly integrate royalty logic directly into their NFT contracts.

OpenZeppelin’s Stellar contracts now support royalties through a dedicated extension available at: OpenZeppelin Royalties Library

This module introduces native support for:

  • Default collection-wide royalties
  • Per-token royalty overrides
  • Royalty information queries based on token ID and sale price

Full code for the example below is available here:
https://github.com/jamesbachini/NFT-Royalties-Example

You can use this with the Soroban playground at https://soropg.com

#![no_std]
use soroban_sdk::{contract, contractimpl, symbol_short, Address, Env, String};
use stellar_access_control::{self as access_control, AccessControl};
use stellar_access_control_macros::{has_role};
use stellar_default_impl_macro::default_impl;
use stellar_non_fungible::{royalties::NonFungibleRoyalties, Base, NonFungibleToken};

#[contract]
pub struct RoyaltyExampleContract;

#[contractimpl]
impl RoyaltyExampleContract {
    pub fn __constructor(e: &Env, admin: Address, manager: Address) {
        Base::set_metadata(
            e,
            String::from_str(e, "https://example.com/nft/"),
            String::from_str(e, "Royalty NFT"),
            String::from_str(e, "RNFT"),
        );

        // Set default royalty for the entire collection (10%)
        Base::set_default_royalty(e, &admin, 100);

        access_control::set_admin(e, &admin);

        // create a role "manager" and grant it to `manager`
        access_control::grant_role_no_auth(e, &admin, &manager, &symbol_short!("manager"));
    }

    pub fn mint(e: &Env, to: Address) -> u32 {
        Base::sequential_mint(e, &to)
    }

    pub fn get_royalty_info(e: &Env, token_id: u32, sale_price: u32) -> (Address, u32) {
        Base::royalty_info(e, token_id, sale_price)
    }
}

#[default_impl]
#[contractimpl]
impl NonFungibleToken for RoyaltyExampleContract {
    type ContractType = Base;
}

#[contractimpl]
impl NonFungibleRoyalties for RoyaltyExampleContract {
    #[has_role(operator, "manager")]
    fn set_default_royalty(e: &Env, receiver: Address, basis_points: u32, operator: Address) {
        Base::set_default_royalty(e, &receiver, basis_points);
    }

    #[has_role(operator, "manager")]
    fn set_token_royalty(
        e: &Env,
        token_id: u32,
        receiver: Address,
        basis_points: u32,
        operator: Address,
    ) {
        Base::set_token_royalty(e, token_id, &receiver, basis_points);
    }

    fn royalty_info(e: &Env, token_id: u32, sale_price: u32) -> (Address, u32) {
        Base::royalty_info(e, token_id, sale_price)
    }
}

#[default_impl]
#[contractimpl]
impl AccessControl for RoyaltyExampleContract {}

Understanding Royalties and Basis Points

The contract uses basis points (bps) to define royalty percentages:

  • 1 basis point = 0.01%
  • 100 basis points = 1%
  • 1000 basis points = 10%

In this example, we configure a default royalty of 1%, assigned to the admin address. This applies to all tokens in the collection unless a specific royalty is set per token.


Internally, the NonFungibleRoyalties trait works as follows:

  • Default royalties are stored and returned unless a token-specific royalty is set.
  • Overrides take precedence when defined per token.
  • The royalty_info function performs the logic to return the right configuration.

Developers don’t need to reimplement royalty logic—the base class handles the complexity.

Marketplace Integration

Note that royalties are not enforced automatically.

Smart contracts only expose royalty info, they do not collect or distribute royalties by default. It’s up to NFT marketplaces to query and enforce these settings during sales.

If an NFT is transferred peer-to-peer (wallet-to-wallet), no royalties are applied. Royalty enforcement only occurs if marketplaces voluntarily support and honor the standard.


Marketplace Adoption on Stellar

As of now, royalty enforcement is not yet common across Stellar NFT marketplaces. However:

  • 🔍 Litemint has indicated in their documentation that they are working on a royalty system.
  • 🚀 Wider adoption is expected as the OpenZeppelin library becomes the standard.

Even if royalties aren’t enforced today, implementing them prepares your contracts for future integrations.

Royalties introduce a sustainable revenue model for creators:

  • Artists and developers earn ongoing revenue as their NFTs are traded.
  • Incentivizes quality content and long-term community engagement.
  • Aligns interest between creators, collectors, and platforms.

By adopting the royalty standard early, your NFT contracts are future-proofed for when marketplaces begin enforcement.


Get The Blockchain Sector Newsletter, binge the YouTube channel and connect with me on Twitter

The Blockchain Sector newsletter goes out a few times a month when there is breaking news or interesting developments to discuss. All the content I produce is free, if you’d like to help please share this content on social media.

Thank you.

James Bachini

Disclaimer: Not a financial advisor, not financial advice. The content I create is to document my journey and for educational and entertainment purposes only. It is not under any circumstances investment advice. I am not an investment or trading professional and am learning myself while still making plenty of mistakes along the way. Any code published is experimental and not production ready to be used for financial transactions. Do your own research and do not play with funds you do not want to lose.


Posted

in

, , , ,

by