Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discussion: NFT Royalties #27

Closed
barnjamin opened this issue Sep 21, 2021 · 19 comments
Closed

Discussion: NFT Royalties #27

barnjamin opened this issue Sep 21, 2021 · 19 comments

Comments

@barnjamin
Copy link
Contributor

This issue is to discuss requirements and possible specification for an enforceable Royalty system for Algorand NFTs.

Please comment with any relevant information or ideas.

If you're already doing this please describe the method you're using.

@whereisrysmind
Copy link

There are several considerations here so far that I see. 1) We need to know the royalty percent, I'm thinking to at least 2 decimals of precision. 2) There should be a way to identify not just the asset creator but a group of creators or collaborators who are also credited. 3) In the case of multiple creators we might want to also to enable weighted distribution of the royalty.

Obviously this would all be optional, and need some information gathering at the time of asset creation, as well as some smart contracts capable of consuming this data.

Im envisioning implementing metadata about people/groups for accreditation thusly:
Person/Organization Schema

{
    "title": "Person, Group or Organization",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "The name of the entity"
        },
        "description": {
            "type": "string",
            "description": "A simple description of the entity"
        },
        "address": {
            "type": "string",
            "description": "Optional blockchain wallet address for the entity"
        }
    }
}

And then somewhere in the Metadata:

...
        "creator": {
            "title": "Person, Group or Organization",
            "description": "The entity"
        },
        "royalty": {
            "type": "number",
            "description": "This value indicates the percentage of sales of this asset which are required by the creator. Value should be set with up to two decimals in precision."
        },
        "contributors": {
            "type": "array",
            "items": {
                "title": "Person, Group, or Organization"
            },
            "description": "List of all credited creators who are to be awarded credit for this work."
        },
...

@barnjamin
Copy link
Contributor Author

Good input, reformatting and commenting here

1) We need to know the royalty percent, I'm thinking to at least 2 decimals of precision. 

Seems reasonable, and that is assumed to be for final sale price right? Preventing asset xfer unless the royalty txn that meets the requirement is attached atomically?

2) There should be a way to identify not just the asset creator but a group of creators or collaborators who are also credited. With the option for weighted distribution of the royalty.

This makes sense. Right now transactions are limited to 16 grouped atomically, so in the case that there are more collaborators than that it will need to go to some multisig or other account.

Also where would you expect the royalty info would be stored? The values will need to be encoded in whatever sc is validating the xfer to ensure the royalty is actually paid.

@JuWeber99
Copy link

Also where would you expect the royalty info would be stored? The values will need to be encoded in whatever sc is validating the xfer to ensure the royalty is actually paid.

At the moment I think therefore an option is to create a stateful smart contract which wraps the asset and where each transaction must be also signed by the asset creator, if the asset should be manageable by him. ( else no multisig needed )

The wrapper contract should be the ManagerAddress to be able to prevent deletion of the asset or some other things the creator whouldnt want event after selling the asset.
In the main them the rightful owner of the Asset should be able to freeze the asset and clawback the asset so he can freeze it and be the only one who can determine where it gets transferred (because he owns it).

The only way to enforce royaltie i can think about at the moment is using a multisignature contract when using a contract that should issue a royaltie payment for an asset with signatures required by at least the app and the wrapped contract containing the royaltie percentage

@cusma
Copy link
Contributor

cusma commented Sep 22, 2021

AlgoRealm game has been built exactly with the purpose of showing how you can embed decentralised royalty policies into a NFT, directly on Layer-1.

This being said, that solution was released before the ARC-4 proposal on ABI, so I think that if I were to propose a solution today, that takes this into account, I would done it differently.

I think we should collectively start thinking about general approaches about "ASA Transfer Control ABIs", whatever this "transfer control" means: it could be a royalty over an NFT, it could be a regulation policy in a given jurisdiction, it could be a decentralised KYC/whitelisting, it could be a policy on maximum transferable amounts, etc. Standardising this approach with ABIs would make also much more simple, in future, to interact with those ASAs directly form wallets.

So, in this case, royalty over NFTs would be just one particular class of ASA Transfer Control ABIs.

I think that for the ASA Royalty ABI:

  1. The ASA subject to the royalty MUST be created as Default Frozen, with no Freeze Address.
  2. The Clawback Address of the ASA subject to the royalty MUST be assigned to an App (this is possible starting from TEAL 5).
  3. The ASA subject to the royalty MUST be only transferred with Clawback.
  4. The Manager Address of the ASA subject to the royalty SHOULD be delated (to avoid unforeseen modification of the royalty ex-post).
  5. The ABI MUST allow the definition of royalty collectors (and maybe SHOULD allow to update them over time)
  6. Given the royalty policy, known in advance, the ABI MUST calculate the royalty amounts on every transaction.
  7. The ABI MUST NOT allow the modification of the royalty policy
  8. The ABI MAY include a whitelisting

@barnjamin
Copy link
Contributor Author

barnjamin commented Sep 22, 2021

"ASA Transfer Control ABIs"

Love it

The ASA subject to the royalty MUST be only transferred with Clawback.

Very interesting, hadn't thought about this. How could this be enforced? Would the ASA have to reside in a contract account?

maybe SHOULD

MIGHT?

It looks like you've thought a lot about this, would you be willing/have time to submit a draft proposal?

@cusma
Copy link
Contributor

cusma commented Sep 22, 2021

Very interesting, hadn't thought about this. How could this be enforced? Would the ASA have to reside in a contract account?

Previous to TEAL 5 the Clawback Address would be assigned to a Contract Account (AlgoRealm solution explains exactly this approach step by step). Form TEAL 5 you can also assign the Clawback Address to the Application Account. Since the ASA is issued as Default Frozen the only way to move it is asking to the Application to transfer the ASA on your behalf with an ABI call, so that you are implicitly enforcing all the Application logic over the ASA transferability.

MIGHT?

Yes, would be better using MAY to comply with RFC-2119.

@ChrisAntaki
Copy link
Contributor

Thanks for sharing your insights @cusma , this seems like a powerful strategy and I like how it keeps using ASAs

@AronTurner
Copy link

Previous to TEAL 5 the Clawback Address would be assigned to a Contract Account (AlgoRealm solution explains exactly this approach step by step). Form TEAL 5 you can also assign the Clawback Address to the Application Account. Since the ASA is issued as Default Frozen the only way to move it is asking to the Application to transfer the ASA on your behalf with an ABI call, so that you are implicitly enforcing all the Application logic over the ASA transferability.

@cusma Wow, very interesting and creative concept!

@hartmutobendorf
Copy link

@JuWeber99
Copy link

AlgoRealm game has been built exactly with the purpose of showing how you can embed decentralised royalty policies into a NFT, directly on Layer-1.

This being said, that solution was released before the ARC-4 proposal on ABI, so I think that if I were to propose a solution today, that takes this into account, I would done it differently.

I think we should collectively start thinking about general approaches about "ASA Transfer Control ABIs", whatever this "transfer control" means: it could be a royalty over an NFT, it could be a regulation policy in a given jurisdiction, it could be a decentralised KYC/whitelisting, it could be a policy on maximum transferable amounts, etc. Standardising this approach with ABIs would make also much more simple, in future, to interact with those ASAs directly form wallets.

So, in this case, royalty over NFTs would be just one particular class of ASA Transfer Control ABIs.

I think that for the ASA Royalty ABI:

1. The ASA subject to the royalty **MUST** be created as Default Frozen, with no Freeze Address.

2. The Clawback Address of the ASA subject to the royalty **MUST** be assigned to an App (this is possible starting from TEAL 5).

3. The ASA subject to the royalty **MUST** be only transferred with Clawback.

4. The Manager Address of the ASA subject to the royalty **SHOULD** be delated (to avoid unforeseen modification of the royalty ex-post).

5. The ABI **MUST** allow the definition of royalty collectors (and maybe **SHOULD** allow to update them over time)

6. Given the royalty policy, known in advance, the ABI **MUST** calculate the royalty amounts on every transaction.

7. The ABI **MUST NOT** allow the modification of the royalty policy

8. The ABI **MAY** include a whitelisting

Whould it make sense to include: creating the asset from the application to enforce DefaultFrozen and no FreezeAddress via an application call on which the AssetConfigTxn gets issued via an InnerTxn on adequate ApplicationCall?

@cusma
Copy link
Contributor

cusma commented Sep 23, 2021

@JuWeber99 I think it would, as long as the App became aware of the newly generated ASA ID: this makes operations over the ASA restricted to a specific ASA ID and so more secure.

Please mind that my proposal was just a draft of me brainstorming with myself, but in principle the ASA Royalty ABI could expose, among the others, a proper mint method with which you can define all the properties and configurations of the ASA subject to the royalty directly within the App.

I'm still thinking about whether the best approach would be having a complete ASA Royalty ABI with its own methods (maybe something like mint, transferRoyalty and optionals like burn, revoke, freezeAccount, freezeAsa and so on) or if would be better to modularise those single methods and combining them in a more composable and generic ASA Transfer ABI within which you may want to include the transferRoyalty but not the revoke one.

There are trade off between the two approaches, my gut feeling is that the second one could be over-engineered (while more modular).

In both cases the practical steps would be:

  1. Defining a comprehensive list of all the desirable ABI methods
  2. Defining specs for each one of those desirable ABI methods

@hartmutobendorf
Copy link

hartmutobendorf commented Sep 23, 2021

@cusma When discussing royalties, we have also come across the idea of "split gain" – it might make it more interesting to trade NFTs and thus increase the fluidity of the market.

Artist "Win Share"

An NFT that enforces a custom transaction when reselling it, the artist "win share" will
calculate the amount of additional value generated (as the delta between the last and the current sale)
and redirect a part of the additional value to the initial owner (aka artist).

The artist will only receive something if an NFT is sold for a higher price than the last recorded price.
When the price is higher, she will get e.g. 20% of the additional value:

  1. an NFT is sold for an initial 10 from A to B, A receives 10
  2. the NFT is sold for 8 from B to C, A receives 0, B receives 8
  3. the NFT is sold for 20 from C to D, A receives 20% of 12 = 2,40, D receives 17,60.

Perhaps, an architecture that has several ASA ABIs the creator of an NFT can choose from would be more open...

@fergalwalsh
Copy link

I've also been thinking about this issue recently and want to share an idea I had.

An ASA that requires royalties is DefaultFrozen: True and specifies a specific address in the FreezeAddress field. This address is the address of a stateful application. The ASA creator opts in to this stateful app and stores a value in local state that represents their royalty percentage.
When making a trade involving the ASA the txn group must include a call to the app and a payment to the creator. The app call will only succeed and issue an inner unfreeze txn if the payment txn is to the correct address and for the correct amount, as determined by looking up the local state of the creator account.

The fully transaction group for a purchase of an ASA created by C from A by B would be:

  • AppCall (with creator in ForeignAccounts)
  • Payment of X Algo from B to A (purchase price)
  • Payment of R Algo from A to C (royalty)
  • Asset Transfer from A to B (ASA)

This could be extended trivially to allow setting/updating a beneficiary for a creator so they do not have to be the same address.

This approach appeals to me because it potentially allows for a single well known royalty manager app that everyone could use simply by opting into and setting the freeze address of ASAs. I also like keeping the existing semantics of transaction groups rather than introducing new methods for this.

Any wallet/app that knows about this well-known app address would be able to determine that an ASA operates this way simply by looking at the freeze address. It would determine the royalty amount and beneficiary by looking up the local state of the ASA creator. Once it knows these it could prepare the appropriate transaction group.

Disadvantages

  • Obviously this assumes a global royalty amount per account. If one wished to create multiple NFTs with different royalties this wouldn't work. This is designed with NFT series in mind where a single 'creator' would be creating 1000s of NFTs from multiple accounts. Each series would likely have the same royalty scheme.
  • Attempting to be a global solution it would by design only handle simple common scenarios.

@rssalessio
Copy link

Hi, I have recently started to delve into this problem, and I agree with @cusma that it just makes much more sense to have a sort of ASA Transfer Control ABI. However, it is also hard to come up with a proposal that works for any scenario. Perhaps it would be better to create a new discussion? @barnjamin

Nonetheless, I am new to the community to Algorand and I would like to contribute somehow to the project.

My proposal is to create an object that is a JSON description of the list of transactions that are required to successfully transfer an ASA. This could help not only with the royalty fees, but also many other things.

  1. The ASA MUST be created as Default Frozen.
  2. The Clawback address of the ASA MUST be assigned to an App or a Smart Signature.
  3. The ASA MUST be only transferred with Clawback.
  4. The ABI description MUST define an ordered group of transactions required to successfully transfer the ASA.
  5. The last transaction of this ordered group MUST be an AXFER transaction, where the sender address is the Clawback address (we need to include this transaction in the group in order to understand who is receiving the asset. Maybe the receiver is in a whitelist/blacklist).
  6. There MAY be more than 1 way to approve the transaction. This means that there MAY be more than one correct group of transactions.
  7. The buyer MAY buy the ASA using another ASA. However, to avoid loops the buyer MUST not use an ASA with a clawback address set, or a frozen ASA.

In general, I believe each transaction SHALL only describe the necessary parameters for that transaction.
If some transaction parameters are variable (e.g. a % royalty fee, and not a pre-defined fixed amount), an App will be called to compute the necessary parameters.

The JSON description would be

interface ASATransferControl {
  assetId: number,                                    // Perhaps redundant
  listTransactionsGroup: Array<TransactionsGroup>     // List of ordered transactions groups
}

where

interface TransactionsGroup {
  buyerAsset: Array<number>,                           // Assets that may be used by the buyer to buy the original ASA
  transactionsGroup: Array<Transaction>                // Ordered group of transactions that defines how to buy the ASA
}

interface Transaction {
  type: string,                                                                 // "appl", "axfer", etc...
  args: { field: string, value: string | number | ... | AppCall | AppRead }     // Any argument that needs to be specified.
}

interface AppCall {                   // This is used to call an App method
  appId: number,
  method: Method,                     // See ARC-0004
  appArgs: Array<string | number>     // List of parameters
}

interface AppRead {                   // Read a global variable
  appId: number,
  variable: string,
  type: string | number
}
  • There may be different ways to accept the transfer. That's why listTransactionsGroup is an array.
  • Interface TransactionsGroup includes buyerAsset. This is a list of ASA IDs that the buyer can use with that specific transactionsGroup.
  • In the Transaction interface the value of field may be AppCall, which means that the value should be computed by calling the method defined in AppCall.
  • Instead AppRead is a simple read of a specific global variable in the App.

Example

For example, imagine the following 2 scenarios:

  1. We want to impose royalty fees on secondary sales of a specific ASA. Let's say this is 3% of the payment amount in ALGO.
  2. Alternatively, we also accept that the ASA is swapped with other different assets. In this case the royalty fee is set to a fixed amount of 1000 algo.

We first create an ASA (id 10) and assign the Clawback address to a specific App (id 123)

Scenario 1 (Pay ASA with ALGO)

To buy the ASA the buyer must perform the following 3 transactions

  1. Make a payment to the App (royalty fee 3%)
  2. Make a payment to the Seller
  3. Make an axfer transaction.

We suppose the Clawback is also the App for simplicity, so I don't include a NoOp transaction.

Scenario 2 (Pay ASA with another ASA)

To buy the ASA the buyer must perform the following 3 transactions

  1. Make a payment to the App (royalty fee of 1 algo)
  2. axfer transaction of the buyer's ASA
  3. Make an axfer transaction of the seller's ASA

We assume the buyer's ASA id can be 11 or 12.

JSON Description

In the JSON description I make use of some variables that can be easily retrieved

  • "$AMOUNT" (the payment amount)
  • "$SELLER" (who is the current ASA seller)
  • "$BUYER" (who is the buyer)
  • "$CLAWBACK_ADDRESS" (clawback of the ASA)

The resulting JSON is as follows.

{
  "assetId": 10,
  "listTransactionsGroup": [
    {                                                   # SCENARIO 1
      "buyerAsset": [0]                                 # AssetID = 0 is ALGO
      "transactionsGroup": [                            # List of transactions
        {
          "type": "pay",                                # Royalty fee payment
          "args": { 
            "rcv": "$CLAWBACK_ADDRESS",                 # The receiver of the fee is the Clawback address
            "amount": {                                 # Here we call the APP to compute the royalty fee
              "appId": 123,
              "method": {
                "name": "computeFee",
                "args": ["number"],
                "returns": {"type": "number"}
                },
              "args": ["$AMOUNT"]                       # We call the "computeFee" method and pass the $AMOUNT parameter
              }
            }
        },
        {
          "type": "pay",                                # Payment transaction, the receiver is the seller
          "args": 
            {"rcv": "$SELLER", "amount": "$AMOUNT"}     # No constant args
        },
        {
          "type": "axfer",                              # Transaction to transfer the asset
          "args":
            {"asnd": "$SELLER",
             "snd": "$CLAWBACK_ADDRESS"
             "xaid": 10,
             "aamt": 1
            },
        }
      ]
    },
    {                                                   # SCENARIO 2
      "buyerAsset": [11, 12],                           # buy the ASA using either ASA 11 or 12
      "transactionsGroup": [                            # List of transactions
        {
          "type": "pay",                                # Royalty fee payment of 1 algo
          "args": 
            {"rcv": "$CLAWBACK_ADDRESS", "amount": 100000}
        },
        {
          "type": "xfer",                               # Transfer to the seller 1 ASA id 11 or 12
          "args": 
            {"snd": "$BUYER", "aamt": 1}                # here the field "xaid" can only be 11 or 12 implicitly (the clawback should check this)
        },
        {
          "type": "axfer",                              # Transaction to transfer the asset
          "args":
            {"asnd": "$SELLER",
             "snd": "$CLAWBACK_ADDRESS"
             "xaid": 10,
             "aamt": 1
            },
        }
      ]
    }
}

Comments:

  • We may not require to specify assetId in the ASATransferControl interface (it feels redundant)
  • IMO the description of the JSON should be allowed to change
  • While writing this proposal I had to define an interface for a transaction, but maybe there exists already something?

@Flawm
Copy link

Flawm commented Dec 29, 2021

I'd have to agree with the approach suggested by @whereisrysmind here. It's similar to how Solana approaches this issue, and their NFT standards & markets are mature. Royalty information is embedded into the asset; that may not be possible - but we can put it into the metadata.

"sellerFeeBasisPoints": 500,
"creators": [
    {"address": "", "share": 0}, 
    {"address": "", "share": 100}
]

sellerFeeBasisPoints is the secondary sale fee in basis points to pay out to the creators. In this case 5%
creators is the list of creators & their respective cut.
address is a creator wallet.
share is the percent the of the seller fee each creator gets. The total share value must be equal to 100.

Markets would be expected to honor this. Their web2 can make a grouped transaction that can easily accommodate this. Additionally, this can work for assets that currently exist if the metadata can be updated.

@runvnc
Copy link

runvnc commented Jan 1, 2022

You can encode some of it compactly in the Reserve Address. I also want to use the Reserve Address for some trait data. Maybe we can share. But the reason I suggest this is because stateful contracts are now Turing complete and very shortly get the ability to call one another so it seems very obvious to anticipate exponential growth in on chain capabilities and expectations. Maybe I am crazy but people are going to want to access some of that data. And remember algod does not do anything with Reserve Address. They should just call it Reserved Data for your Algorand App.
To prevent confusion could specify encoding scheme in metadata. I guess I just wanted to say, I love you Reserve Address. Holds so many if my hopes and dreams for on-chain processing. But seriously is there an alternative I am missing for accessing relevant data in a smart contract?

@barnjamin
Copy link
Contributor Author

Please review and comment here: #70

@barnjamin
Copy link
Contributor Author

@rssalessio It looks like you put a lot of thought into the above comment, please take a look at #70 if you have time.

@SudoWeezy
Copy link
Collaborator

Closing this, the discussion is now here ARC-18 Royalty Enforcement

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

No branches or pull requests