帖子
分享您的知识。
On-Chain Royalty Systems in Move: Design Patterns and Marketplace Compatibility
Deep dive into enforcing NFT royalties via Move modules. Let’s explore how to manage multi-recipient splits, royalty upgrades, and ensure compliance across Sui marketplaces.
Question: Should royalty logic live inside each NFT or in a shared manager?
- Move CLI
- Move
- Move Module
答案
2When you design on-chain royalty systems in Move for Sui, the biggest choice is whether to embed the royalty logic directly into each NFT object or to centralize it in a shared royalty manager module. Both patterns work, but they have different trade-offs for flexibility, gas efficiency, and marketplace compatibility.
If you store royalty data inside each NFT, you ensure that royalty rules travel with the asset itself. This makes royalties self-sovereign, meaning even if the NFT is transferred outside of your marketplace, the data is still present. However, enforcing the royalty during resale requires marketplaces to respect and read this field, which is not guaranteed unless there’s a shared convention.
A shared royalty manager module, on the other hand, provides a single source of truth for royalty enforcement. You can register NFTs and link them to royalty rules (percentages, recipients, splits). This design allows you to update or extend royalty logic without modifying existing NFTs, and it ensures multiple marketplaces can integrate with one standard. The trade-off is that the NFT depends on the manager’s availability, and upgrades must be handled carefully to avoid breaking compliance.
Here’s a simplified Move sketch for a shared manager:
module royalties::manager {
use sui::tx_context::TxContext;
use sui::object;
struct RoyaltyRule has key, store {
recipients: vector<address>,
shares: vector<u64>, // percentages in basis points (10000 = 100%)
}
public fun register_rule(nft_id: object::ID, recipients: vector<address>, shares: vector<u64>, ctx: &mut TxContext): RoyaltyRule {
assert!(vector::length(&recipients) == vector::length(&shares), 0);
object::new<RoyaltyRule>(RoyaltyRule { recipients, shares }, ctx)
}
public fun calculate_payout(rule: &RoyaltyRule, sale_price: u64): vector<u64> {
let mut payouts = vector::empty<u64>();
let len = vector::length(&rule.shares);
let mut i = 0;
while (i < len) {
let share = *vector::borrow(&rule.shares, i);
let payout = (sale_price * share) / 10000;
vector::push_back(&mut payouts, payout);
i = i + 1;
};
payouts
}
}
Best practices you can follow:
- Use basis points instead of floating percentages to avoid precision errors.
- If you allow multi-recipient splits, make sure the sum of shares is exactly
10000
to prevent overpayment. - For upgradeability, use a manager contract that marketplaces call instead of rewriting NFTs.
- To increase compatibility, follow shared standards (like SIP-010 royalty proposal) so external marketplaces know how to enforce payouts.
- Royalty logic inside each NFT
Every NFT carries its own royalty policy (e.g., percentage, recipient list) as fields or via a capability object.
Benefits: self-contained, no external dependency, royalties travel with the asset even if moved outside a marketplace.
Drawbacks: harder to update (once minted, royalty rules are frozen unless you build upgradeable indirection), more storage overhead per NFT, and every marketplace must explicitly respect the fields.
- Royalty manager as a shared module
NFTs point to a shared RoyaltyManager object that stores rules per collection or per creator.
Benefits: central enforcement, easy to update rules (e.g., multi-recipient splits, changes to rates), less per-NFT storage.
Drawbacks: marketplaces need to integrate with this manager consistently, and if bypassed, royalties won’t automatically trigger.
Best practice on Sui
Collections usually define a shared royalty manager and issue NFTs that reference it, since this scales better and enables flexible splits.
If you need immutability guarantees, you can “freeze” the royalty record by revoking the creator’s update capability, ensuring future-proof enforcement.
To maximize marketplace compatibility, follow Sui standards (e.g., 0x2::royalty proposals) and emit royalty-related events so off-chain indexers can enforce policies consistently.
你知道答案吗?
请登录并分享。
Move is an executable bytecode language used to implement custom transactions and smart contracts.