-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 2f06df9
Showing
10 changed files
with
998 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
) | ||
) |
Oops, something went wrong.