stacksgov / sips

Community-submitted Stacks Improvement Proposals (SIPs)
131 stars 80 forks source link

Trait for minting NFT's with any FT and STX using unified interface #60

Closed LNow closed 2 years ago

LNow commented 2 years ago

As the ecosystem grows we see more and more NFT contracts that can be minted not only with plain STX but also with different FT (ie. CityCoins). Each have a slightly different function name and signature. I think we can do better.

There should be a trait that defines unified interface which can be used to mint new NFT with STX and any SIP-010 compliant FT. Such trait could look like this:

(use-trait sip-010-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)

(define-trait (
    ;; mints 1 NFT using supplied FT as tender
    (mint-with (<sip-010-trait>) (response bool uint))

    ;; mints X NFTs using supplied FT as tender
    (mint-many-with (uint <sip-010-trait>) (response bool uint))

    ;; returns minting price in supplied FT
    (get-mint-price-in (<sip-010-trait>) (response uint uint))
))

And to make it work also for STX we could define global wrapped STX SIP-010 compliant FT that is not issuing new token, but uses plain STX:

(impl-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)

(define-read-only (get-balance (owner principal))
    (ok (stx-get-balance owner))
)

(define-read-only (get-decimals)
    (ok u6)
)

(define-read-only (get-name)
    (ok "Wrapped STX")
)

(define-read-only (get-symbol)
    (ok "WSTX")
)

(define-read-only (get-token-uri)
    (ok (some u"https://www.stacks.co"))
)

(define-read-only (get-total-supply)
    (ok stx-liquid-supply)
)

(define-public (transfer (amount uint) (sender principal) (recipient principal) (memo (optional (buff 34))))
    (begin
        (try! (stx-transfer? amount sender recipient))
        (match memo to-print (print to-print) 0x)
        (ok true)
    )
)

Very similar approach can be used by NFT marketplaces to define unified interface for listing/buying NFTs for STX and any SIP-010 compliant FT.

@Jamil @friedger @dantrevino @MarvinJanssen @radicleart

friedger commented 2 years ago

@LNow Yes, please! That is a good proposal. We should also add standard error codes for

I would like to move from sip-10 to sip-transferable (#52)

For wrapped stx

I suggest to not include stx minting here but define mint-with-ustx, etc.

radicleart commented 2 years ago

Can this approach work with Arkadikos or ALEX's wrapped bitcoin as in SP3DX3H4FEYZJZ586MFBS25ZW3HZDMEW92260R2PR.Wrapped-Bitcoin SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.token-wbtc ?

radicleart commented 2 years ago

Does this suggestion also impact sip-0013 ?

LNow commented 2 years ago

@friedger

The problem with stx as a sip-10 is that it can't be operated. Implementations might need to implement two cases anyway.

What do you mean when you say it can't be operated and why you might need to implement two cases?

Also the UI for post-conditions would need to handle two different cases. Yes, UI will have to generate two different types of post-conditions. One when someone choose wstx and another one when other FT is used.

With regards to wSTX name, I'm not attached to it. We can change it to STX-FT or anything else.

I suggest to not include stx minting here but define mint-with-ustx, etc.

The key concept of this idea is to not have separate function for ustx or anything else. Instead of implementing different logic for STX, and different for FT you'll have just one. Example: https://github.com/LNow/clarity-projects/blob/2163db81b9286f0c30b00dd7728fb0e93dfc6243/contracts/tokens/mintable-nft-token.clar#L42-L59

One function to rule them all

LNow commented 2 years ago

@radicleart that's the thing - it will work with any fungible token that is compliant with SIP-010. Citycoins, USDA, DIKO, Nothing, wbtc, xbtc, wrapped-bitcoin, ANY.

Does this suggestion also impact sip-0013 ?

Sure, why not. I want this to be a separate trait that can be used in any contract that you think it can be used in.

radicleart commented 2 years ago

@LNow i think most people (me for sure, stxnft i believe) are following this for the commission since crash punks

(define-trait commission
    ((pay (uint uint) (response bool uint)))
)

which could be generalised to...

(use-trait sip-010-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)

(define-trait commission
    ((pay (<sip-010-trait> uint uint) (response bool uint)))
)

and the listing and buying methods also need to be generalised...

(define-public (list-in-ustx (id uint) (price uint) (comm <commission-trait>))

to

(define-public (list-in-token (id uint) (price uint) (comm <commission-trait>) (token <sip-010-trait>))...

likewise for the buy-in-ustx ?

LNow commented 2 years ago

@radicleart Yes, generalized version of #51 would look like you described it.

radicleart commented 2 years ago

@LNow so is the answer to @friedger on this point?

The key concept of this idea is to not have separate function for ustx or anything else. Instead of implementing different logic for STX, and different for FT you'll have just one. Example: https://github.com/LNow/clarity-projects/blob/2163db81b9286f0c30b00dd7728fb0e93dfc6243/contracts/tokens/mintable-nft-token.clar#L42-L59

that at this line

    (and (> price u0) (try! (contract-call? tender transfer price tx-sender artistAddress none)))
    (and (> commission u0) (try! (contract-call? tender transfer commission tx-sender commissionAddress none)))

the tender contract passed in is an ft trait impl that just implements transfer as stx-transfer? as opposed to ft-transfer ?

Hence only one function needed for both transfer types in the main contract?

LNow commented 2 years ago

the tender contract passed in is an ft trait impl that just implements transfer as stx-transfer? as opposed to ft-transfer ? Hence only one function needed for both transfer types in the main contract?

Yes

friedger commented 2 years ago

How do you build post conditions for these txs if they can transfer either STXs or FTs?

LNow commented 2 years ago

The same way Alex/Arkadiko/Stackswaps are doing it. Based on token address user picked on the UI. With one additional if statement. If user choose wrapped stx -> STX post-condition, otherwise -> FT post-condition.

MarvinJanssen commented 2 years ago

Great idea. Is there a way to allow for flexibility in payment schedule? When the Jellyboo NFT project was launched, it featured a pay-what-you-want model to mint (with a set minimum). The standard would be a lot more flexibility if you can set a price per NFT in mint-with and mint-with-many. Then it is also possible to introduce discounts in cases. For projects with a fixed price you just specify whatever get-mint-price-in returns.

LNow commented 2 years ago

@MarvinJanssen something like this?

(define-public (mint-with (tender <sip-010-trait>) (customPrice (optional uint)))
  (let
    (
      (pricing (unwrap! (map-get? TenderPricing (contract-of tender)) ERR_UNKNOWN_TENDER))
      (price (match customPrice price (max price (get price pricing)) (get price pricing)))
      ;; remaining local variables
    )
    ;; minting operations
    (ok true)
  )
)

(define-private (max (a uint) (b uint))
  (if (> a b) a b)
)

or

(define-public (mint-with (tender <sip-010-trait>) (customPrice uint))
  (let
    (
      (pricing (unwrap! (map-get? TenderPricing (contract-of tender)) ERR_UNKNOWN_TENDER))
      (price (max customPrice (get price pricing)))
      ;; remaining local variables
    )
    ;; minting operations
    (ok true)
  )
)

(define-private (max (a uint) (b uint))
  (if (> a b) a b)
)

with extra function that returns price if it has been set:

(define-read-only (get-mint-price-in (tender <sip-010-trait>))
  (get price (map-get? TenderPricing (contract-of tender)))
)
unclemantis commented 2 years ago

I LOVE IT!

friedger commented 2 years ago

We could even extend it to tradables which would include nfts

radicleart commented 2 years ago

Started on this here @friedger - see list-in-token and buy-in-token ?