Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
proiacm committed Mar 2, 2022
0 parents commit 2f06df9
Show file tree
Hide file tree
Showing 10 changed files with 998 additions and 0 deletions.
36 changes: 36 additions & 0 deletions Clarinet.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[project]
name = "tiny-market"
authors = []
description = ""
telemetry = true
requirements = []
analysis = ["check_checker"]
costs_version = 2

[contracts.sip009-nft]
path = "contracts/sip009-nft.clar"
depends_on = ["sip009-nft-trait"]

[contracts.bogus-nft]
path = "contracts/sip009-nft.clar"
depends_on = ["sip009-nft-trait"]

[contracts.sip009-nft-trait]
path = "contracts/sip009-nft-trait.clar"
depends_on = []

[contracts.sip010-ft-trait]
path = "contracts/sip010-ft-trait.clar"
depends_on = []

[contracts.sip010-token]
path = "contracts/sip010-token.clar"
depends_on = ["sip010-ft-trait"]

[contracts.bogus-ft]
path = "contracts/sip010-token.clar"
depends_on = ["sip010-ft-trait"]

[contracts.tiny-market]
path = "contracts/tiny-market.clar"
depends_on = ["sip009-nft-trait", "sip010-ft-trait"]
21 changes: 21 additions & 0 deletions contracts/sip009-nft-trait.clar
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
;; sip009 must conform to specific implementation
;; Clarity does not follow a class hierarchy model, instead it uses traits
;; which define public interface of contract
(define-trait sip009-nft-trait
(
;; Last token ID, limited to uint range
(get-last-token-id () (response uint uint))

;; URI for metadata associated with the token
(get-token-uri (uint) (response (optional (string-ascii 256)) uint))

;; Owner of a given token identifier
(get-owner (uint) (response (optional principal) uint))

;; Transfer from the sender to a new principal
(transfer (uint principal principal) (response bool uint))
)
)
;; parameters are not explicitly defined by name because only the type is important
;; the defined parameters can be found in sip documentation
;; eg. transfer: uint = token id, principal = sender, principal = recipient
52 changes: 52 additions & 0 deletions contracts/sip009-nft.clar
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
;; defines trait to be implemented in the contract
;; implement the contract that the trait is in
;; then identify the trait by the name (since there can be more than one in the contract)
(impl-trait .sip009-nft-trait.sip009-nft-trait)

;; SIP009 NFT trait on mainnet
;; (impl-trait 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait)

;; define nft and identifier type
(define-non-fungible-token sip009-nft uint)

;; tx-sender is a built in keyword that always refers to whoever sent the transaction
(define-constant contract-owner tx-sender)
(define-constant err-owner-only (err u100))
(define-constant err-not-token-owner (err u101))
(define-data-var last-token-id uint u0)

;; return a response with the id of the last minted token
(define-public (get-last-token-id)
(ok (var-get last-token-id))
)

(define-read-only (get-owner (token-id uint))
(ok (nft-get-owner? sip009-nft token-id))
)

(define-read-only (get-token-uri (token-id uint))
(ok none)
)

;; begin used to add more than one expression
(define-public (transfer (token-id uint) (sender principal) (recipient principal))
(begin
(asserts! (is-eq tx-sender sender) err-not-token-owner)
(nft-transfer? sip009-nft token-id sender recipient)
)
)

;; mint a token
(define-public (mint (recipient principal))
(let
(
;; increment token id by 1
(token-id (+ (var-get last-token-id) u1))
)
;; only contract owner can mint (for testing)
(asserts! (is-eq tx-sender contract-owner) err-owner-only)
(try! (nft-mint? sip009-nft token-id recipient))
(var-set last-token-id token-id)
(ok token-id)
)
)
24 changes: 24 additions & 0 deletions contracts/sip010-ft-trait.clar
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
(define-trait sip010-ft-trait
(
;; Transfer from the caller to a new principal
(transfer (uint principal principal (optional (buff 34))) (response bool uint))

;; the human readable name of the token
(get-name () (response (string-ascii 32) uint))

;; the ticker symbol, or empty if none
(get-symbol () (response (string-ascii 32) uint))

;; the number of decimals used, e.g. 6 would mean 1_000_000 represents 1 token
(get-decimals () (response uint uint))

;; the balance of the passed principal
(get-balance (principal) (response uint uint))

;; the current total supply (which does not need to be a constant)
(get-total-supply () (response uint uint))

;; an optional URI that represents metadata of this token
(get-token-uri () (response (optional (string-utf8 256)) uint))
)
)
51 changes: 51 additions & 0 deletions contracts/sip010-token.clar
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
(impl-trait .sip010-ft-trait.sip010-ft-trait)

;; SIP010 trait on mainnet
;; (impl-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)

(define-constant contract-owner tx-sender)
(define-constant err-owner-only (err u100))
(define-constant err-not-token-owner (err u101))

;; No maximum supply!
(define-fungible-token sip010-token)

(define-public (transfer (amount uint) (sender principal) (recipient principal) (memo (optional (buff 34))))
(begin
(asserts! (is-eq tx-sender sender) err-owner-only)
(try! (ft-transfer? sip010-token amount sender recipient))
(match memo to-print (print to-print) 0x)
(ok true)
)
)

(define-read-only (get-name)
(ok "Clarity Coin")
)

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

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

(define-read-only (get-balance (who principal))
(ok (ft-get-balance sip010-token who))
)

(define-read-only (get-total-supply)
(ok (ft-get-supply sip010-token))
)

(define-read-only (get-token-uri)
(ok none)
)

(define-public (mint (amount uint) (recipient principal))
(begin
(asserts! (is-eq tx-sender contract-owner) err-owner-only)
(ft-mint? sip010-token amount recipient)
)
)
176 changes: 176 additions & 0 deletions contracts/tiny-market.clar
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
;; import sip traits
(use-trait nft-trait .sip009-nft-trait.sip009-nft-trait)
(use-trait ft-trait .sip010-ft-trait.sip010-ft-trait)

;; define contract owner
(define-constant contract-owner tx-sender)

;; Think about the various error states that exist in our marketplace.
;; Listing an NFT may fail under a number of circumstances; namely,
;; the expiry block height is in the past, or the listing price is zero (we will not allow free listings).
;; There is also the consideration of the using trying to list the NFT not actually owning it,
;; but this will be handled by the NFT contract itself.
;; listing errors
(define-constant err-expiry-in-past (err u1000))
(define-constant err-price-zero (err u1001))

;; When it comes to cancelling and fulfilling, there are a few more error conditions we can identify:
;; The listing the tx-sender wants to cancel or fulfil does not exist.
;; The tx-sender tries to cancel a listing it did not create.
;; The listing the tx-sender tries to fill has expired.
;; The provided NFT asset trait reference does not match the NFT contract of the listing.
;; Since trait references cannot be stored directly in Clarity, they will have to be provided again when
;; the buyer is trying to purchase an NFT. We have to make sure that the trait reference provided by the buyer
;; matches the NFT contract provided by the seller.
;; The provided payment asset trait reference does not match the payment asset contract of the listing.
;; The same as the above but for the SIP010 being used to purchase the NFT.
;; The maker and the taker (seller and the buyer) are equal. We will not permit users to purchase tokens
;; from themselves using the same principal.
;; The buyer is not the intended taker. If the seller defines an intended taker (buyer) for the listing,
;; then only that principal can fulfil the listing.
;; cancelling and fulfilling errors
(define-constant err-unknown-listing (err u2000))
(define-constant err-unauthorised (err u2001))
(define-constant err-listing-expired (err u2002))
(define-constant err-nft-asset-mismatch (err u2003))
(define-constant err-payment-asset-mismatch (err u2004))
(define-constant err-maker-taker-equal (err u2005))
(define-constant err-unintended-taker (err u2006))

;; Finally, we will implement a whitelist for NFT and payment asset contracts that the contract deployer controls.
;; It makes for two additional error conditions:
;; The NFT asset the seller is trying to list is not whitelisted.
;; The requested payment asset is not whitelisted.
(define-constant err-asset-contract-not-whitelisted (err u2007))
(define-constant err-payment-contract-not-whitelisted (err u2008))

;; The most efficient way to store the individual listings is by using a data map that uses
;; an unsigned integer as a key. The integer functions as a unique identifier and will increment
;; for each new listing. We will never reuse a value. To track the latest listing ID, we will use a simple data variable.
(define-map listings
uint
{
maker: principal,
taker: (optional principal),
token-id: uint,
nft-asset-contract: principal,
expiry: uint,
price: uint,
;; It is important to utilise the native types in Clarity to the fullest extent possible.
;; A listing does not need to have an intended taker, so we make it optional.
payment-asset-contract: (optional principal)
}
)

(define-data-var listing-nonce uint u0)

;; The whitelist itself is a simple map that stores a boolean for a given contract principal.
;; A guarded public function set-whitelisted is used to update the whitelist and a read-only function
;; is-whitelisted allows anyone to check if a particular contract is whitelisted or not.
;; We will also use is-whitelisted to guard other public functions later.
(define-map whitelisted-asset-contracts principal bool)

(define-read-only (is-whitelisted (asset-contract principal))
(default-to false (map-get? whitelisted-asset-contracts asset-contract))
)

(define-public (set-whitelisted (asset-contract principal) (whitelisted bool))
(begin
(asserts! (is-eq contract-owner tx-sender) err-unauthorised)
(ok (map-set whitelisted-asset-contracts asset-contract whitelisted))
)
)
;; take a trait reference (either SIP009 or SIP010) and then do the proper contract-call? to transfer the token.
(define-private (transfer-nft (token-contract <nft-trait>) (token-id uint) (sender principal) (recipient principal))
(contract-call? token-contract transfer token-id sender recipient)
)

(define-private (transfer-ft (token-contract <ft-trait>) (amount uint) (sender principal) (recipient principal))
(contract-call? token-contract transfer amount sender recipient none)
)

;; Retrieve the current listing ID to use by reading the listing-nonce variable.
;; Assert that the NFT asset is whitelisted.
;; Assert that the provided expiry height is somewhere in the future.
;; Assert that the listing price is larger than zero.
;; If a payment asset is given, assert that it is whitelisted.
;; Transfer the NFT from the tx-sender to the marketplace.
;; Store the listing information in the listings data map.
;; Increment the listing-nonce variable.
;; Return an ok to materialise the changes.
(define-public (list-asset (nft-asset-contract <nft-trait>) (nft-asset {taker: (optional principal), token-id: uint, expiry: uint, price: uint, payment-asset-contract: (optional principal)}))
(let ((listing-id (var-get listing-nonce)))
(asserts! (is-whitelisted (contract-of nft-asset-contract)) err-asset-contract-not-whitelisted)
(asserts! (> (get expiry nft-asset) block-height) err-expiry-in-past)
(asserts! (> (get price nft-asset) u0) err-price-zero)
(asserts! (match (get payment-asset-contract nft-asset) payment-asset (is-whitelisted payment-asset) true) err-payment-contract-not-whitelisted)
(try! (transfer-nft nft-asset-contract (get token-id nft-asset) tx-sender (as-contract tx-sender)))
(map-set listings listing-id (merge {maker: tx-sender, nft-asset-contract: (contract-of nft-asset-contract)} nft-asset))
(var-set listing-nonce (+ listing-id u1))
(ok listing-id)
)
)

(define-read-only (get-listing (listing-id uint))
(map-get? listings listing-id)
)

;; A listing is available until it either expires or is cancelled by the maker.
;; When the maker cancels the listing, all that has to happen is for the marketplace to send
;; the NFT back and delete the listing from the data map. The maker only has to provide the
;; listing ID and the NFT asset contract trait reference. The rest can be read from the data map.
(define-public (cancel-listing (listing-id uint) (nft-asset-contract <nft-trait>))
(let (
(listing (unwrap! (map-get? listings listing-id) err-unknown-listing))
(maker (get maker listing))
)
(asserts! (is-eq maker tx-sender) err-unauthorised)
(asserts! (is-eq (get nft-asset-contract listing) (contract-of nft-asset-contract)) err-nft-asset-mismatch)
(map-delete listings listing-id)
(as-contract (transfer-nft nft-asset-contract (get token-id listing) tx-sender maker))
)
)

;; Retrieve the listing from the listings data map and abort if it does not exist.
;; Assert that the taker is not equal to the maker.
;; Assert that the expiry block height has not been reached.
;; Assert that the provided NFT trait reference is equal to the principal stored in the listing.
;; Assert that the payment asset trait reference, if any, is equal to the one stored in the listing.
;; Transfer the NFT from the contract to the buyer and the payment asset from the buyer to the seller and revert if either transfer fails.
;; Delete the listing from the listings data map.
(define-private (assert-can-fulfil (nft-asset-contract principal) (payment-asset-contract (optional principal)) (listing {maker: principal, taker: (optional principal), token-id: uint, nft-asset-contract: principal, expiry: uint, price: uint, payment-asset-contract: (optional principal)}))
(begin
(asserts! (not (is-eq (get maker listing) tx-sender)) err-maker-taker-equal)
(asserts! (match (get taker listing) intended-taker (is-eq intended-taker tx-sender) true) err-unintended-taker)
(asserts! (< block-height (get expiry listing)) err-listing-expired)
(asserts! (is-eq (get nft-asset-contract listing) nft-asset-contract) err-nft-asset-mismatch)
(asserts! (is-eq (get payment-asset-contract listing) payment-asset-contract) err-payment-asset-mismatch)
(ok true)
)
)

(define-public (fulfil-listing-stx (listing-id uint) (nft-asset-contract <nft-trait>))
(let (
(listing (unwrap! (map-get? listings listing-id) err-unknown-listing))
(taker tx-sender)
)
(try! (assert-can-fulfil (contract-of nft-asset-contract) none listing))
(try! (as-contract (transfer-nft nft-asset-contract (get token-id listing) tx-sender taker)))
(try! (stx-transfer? (get price listing) taker (get maker listing)))
(map-delete listings listing-id)
(ok listing-id)
)
)

(define-public (fulfil-listing-ft (listing-id uint) (nft-asset-contract <nft-trait>) (payment-asset-contract <ft-trait>))
(let (
(listing (unwrap! (map-get? listings listing-id) err-unknown-listing))
(taker tx-sender)
)
(try! (assert-can-fulfil (contract-of nft-asset-contract) (some (contract-of payment-asset-contract)) listing))
(try! (as-contract (transfer-nft nft-asset-contract (get token-id listing) tx-sender taker)))
(try! (transfer-ft payment-asset-contract (get price listing) taker (get maker listing)))
(map-delete listings listing-id)
(ok listing-id)
)
)
Loading

0 comments on commit 2f06df9

Please sign in to comment.