Skip to content

Commit

Permalink
feat: new fractional NFT examples (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarvinJanssen committed Feb 1, 2022
1 parent a73158f commit 415bd05
Show file tree
Hide file tree
Showing 6 changed files with 338 additions and 1 deletion.
11 changes: 10 additions & 1 deletion Clarinet.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
[project]
name = "stx-semi-fungible-token"
requirements = []
costs_version = 2

[contracts.fractional-nft]
path = "contracts/examples/fractional-nft.clar"
depends_on = ["sip013-semi-fungible-token-trait"]

[contracts.fractional-sip009-sft]
path = "contracts/examples/fractional-sip009-sft.clar"
depends_on = ["sip013-semi-fungible-token-trait"]

[contracts.semi-fungible-token]
path = "contracts/semi-fungible-token.clar"
Expand All @@ -19,5 +28,5 @@ path = "contracts/test/test-sip010.clar"
depends_on = ["sip010-ft-trait"]

[contracts.wrapped-sip010-sft]
path = "contracts/wrapped-sip010-sft.clar"
path = "contracts/examples/wrapped-sip010-sft.clar"
depends_on = ["sip013-semi-fungible-token-trait"]
113 changes: 113 additions & 0 deletions contracts/examples/fractional-nft.clar
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
(impl-trait .sip013-semi-fungible-token-trait.sip013-semi-fungible-token-trait)

(define-fungible-token fractional-nft)
(define-map token-balances {token-id: uint, owner: principal} uint)
(define-map token-supplies uint uint)

(define-constant contract-owner tx-sender)

(define-constant err-owner-only (err u100))
(define-constant err-unknown-token (err u101))
(define-constant err-cannot-be-zero (err u102))
(define-constant err-insufficient-balance (err u1))
(define-constant err-invalid-sender (err u4))

(define-private (set-balance (token-id uint) (balance uint) (owner principal))
(map-set token-balances {token-id: token-id, owner: owner} balance)
)

(define-private (get-balance-uint (token-id uint) (who principal))
(default-to u0 (map-get? token-balances {token-id: token-id, owner: who}))
)

(define-read-only (get-balance (token-id uint) (who principal))
(ok (get-balance-uint token-id who))
)

(define-read-only (get-overall-balance (who principal))
(ok (ft-get-balance fractional-nft who))
)

(define-read-only (get-total-supply (token-id uint))
(ok (default-to u0 (map-get? token-supplies token-id)))
)

(define-read-only (get-overall-supply)
(ok (ft-get-supply fractional-nft))
)

(define-read-only (get-decimals (token-id uint))
(ok u0)
)

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

(define-public (transfer (token-id uint) (amount uint) (sender principal) (recipient principal))
(let
(
(sender-balance (get-balance-uint token-id sender))
)
(asserts! (is-eq tx-sender sender) err-invalid-sender)
(asserts! (<= amount sender-balance) err-insufficient-balance)
(try! (ft-transfer? fractional-nft amount sender recipient))
(set-balance token-id (- sender-balance amount) sender)
(set-balance token-id (+ (get-balance-uint token-id recipient) amount) recipient)
(print {type: "sft_transfer_event", token-id: token-id, amount: amount, sender: sender, recipient: recipient})
(ok true)
)
)

(define-public (transfer-memo (token-id uint) (amount uint) (sender principal) (recipient principal) (memo (buff 34)))
(begin
(try! (transfer token-id amount sender recipient))
(print memo)
(ok true)
)
)

(define-private (transfer-many-iter (item {token-id: uint, amount: uint, sender: principal, recipient: principal}) (previous-response (response bool uint)))
(match previous-response prev-ok (transfer (get token-id item) (get amount item) (get sender item) (get recipient item)) prev-err previous-response)
)

(define-public (transfer-many (transfers (list 100 {token-id: uint, amount: uint, sender: principal, recipient: principal})))
(fold transfer-many-iter transfers (ok true))
)

(define-private (transfer-many-memo-iter (item {token-id: uint, amount: uint, sender: principal, recipient: principal, memo: (buff 34)}) (previous-response (response bool uint)))
(match previous-response prev-ok (transfer-memo (get token-id item) (get amount item) (get sender item) (get recipient item) (get memo item)) prev-err previous-response)
)

(define-public (transfer-many-memo (transfers (list 100 {token-id: uint, amount: uint, sender: principal, recipient: principal, memo: (buff 34)})))
(fold transfer-many-memo-iter transfers (ok true))
)

(define-public (fractionalise (token-id uint) (fractions uint))
(let
(
(sender-balance (get-balance-uint token-id tx-sender))
(total-supply (default-to u0 (map-get? token-supplies token-id)))
)
(asserts! (> total-supply u0) err-unknown-token)
(asserts! (> fractions u0) err-cannot-be-zero)
(asserts! (is-eq total-supply sender-balance) err-insufficient-balance)
(try! (ft-burn? fractional-nft total-supply tx-sender))
(try! (ft-mint? fractional-nft fractions tx-sender))
(set-balance token-id fractions tx-sender)
(print {type: "sft_burn_event", token-id: token-id, amount: total-supply, sender: tx-sender})
(print {type: "sft_mint_event", token-id: token-id, amount: fractions, recipient: tx-sender})
(ok true)
)
)

(define-public (mint (token-id uint) (recipient principal))
(begin
(asserts! (is-eq tx-sender contract-owner) err-owner-only)
(try! (ft-mint? fractional-nft u1 recipient))
(set-balance token-id (+ (get-balance-uint token-id recipient) u1) recipient)
(map-set token-supplies token-id (+ (unwrap-panic (get-total-supply token-id)) u1))
(print {type: "sft_mint_event", token-id: token-id, amount: u1, recipient: recipient})
(ok true)
)
)
163 changes: 163 additions & 0 deletions contracts/examples/fractional-sip009-sft.clar
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
(impl-trait .sip013-semi-fungible-token-trait.sip013-semi-fungible-token-trait)

(define-constant contract-owner tx-sender)

(define-fungible-token fractional-sip009-sft)
(define-map token-balances {token-id: uint, owner: principal} uint)
(define-map token-supplies uint uint)
(define-map token-decimals uint uint)
(define-map nft-token-ids {asset-contract: principal, nft-token-id: uint} uint)
(define-map token-id-original-nft-id uint uint)
(define-map asset-contract-whitelist principal bool)
(define-data-var token-id-nonce uint u0)

(define-constant err-owner-only (err u100))
(define-constant err-not-whitelisted (err u101))
(define-constant err-unknown-token (err u102))
(define-constant err-insufficient-balance (err u1))
(define-constant err-invalid-sender (err u4))

(define-trait sip009-transferable-trait
(
(transfer (uint principal principal) (response bool uint))
)
)

(define-private (set-balance (token-id uint) (balance uint) (owner principal))
(map-set token-balances {token-id: token-id, owner: owner} balance)
)

(define-private (get-balance-uint (token-id uint) (who principal))
(default-to u0 (map-get? token-balances {token-id: token-id, owner: who}))
)

(define-read-only (get-balance (token-id uint) (who principal))
(ok (get-balance-uint token-id who))
)

(define-read-only (get-overall-balance (who principal))
(ok (ft-get-balance fractional-sip009-sft who))
)

(define-read-only (get-total-supply (token-id uint))
(ok (default-to u0 (map-get? token-supplies token-id)))
)

(define-read-only (get-overall-supply)
(ok (ft-get-supply fractional-sip009-sft))
)

(define-read-only (get-decimals (token-id uint))
(ok (default-to u0 (map-get? token-decimals token-id)))
)

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

(define-public (transfer (token-id uint) (amount uint) (sender principal) (recipient principal))
(let
(
(sender-balance (get-balance-uint token-id sender))
)
(asserts! (is-eq tx-sender sender) err-invalid-sender)
(asserts! (<= amount sender-balance) err-insufficient-balance)
(try! (ft-transfer? fractional-sip009-sft amount sender recipient))
(set-balance token-id (- sender-balance amount) sender)
(set-balance token-id (+ (get-balance-uint token-id recipient) amount) recipient)
(print {type: "sft_transfer_event", token-id: token-id, amount: amount, sender: sender, recipient: recipient})
(ok true)
)
)

(define-public (transfer-memo (token-id uint) (amount uint) (sender principal) (recipient principal) (memo (buff 34)))
(begin
(try! (transfer token-id amount sender recipient))
(print memo)
(ok true)
)
)

(define-private (transfer-many-iter (item {token-id: uint, amount: uint, sender: principal, recipient: principal}) (previous-response (response bool uint)))
(match previous-response prev-ok (transfer (get token-id item) (get amount item) (get sender item) (get recipient item)) prev-err previous-response)
)

(define-public (transfer-many (transfers (list 200 {token-id: uint, amount: uint, sender: principal, recipient: principal})))
(fold transfer-many-iter transfers (ok true))
)

(define-private (transfer-many-memo-iter (item {token-id: uint, amount: uint, sender: principal, recipient: principal, memo: (buff 34)}) (previous-response (response bool uint)))
(match previous-response prev-ok (transfer-memo (get token-id item) (get amount item) (get sender item) (get recipient item) (get memo item)) prev-err previous-response)
)

(define-public (transfer-many-memo (transfers (list 200 {token-id: uint, amount: uint, sender: principal, recipient: principal, memo: (buff 34)})))
(fold transfer-many-memo-iter transfers (ok true))
)

;; Fractionalising and combining logic

(define-read-only (get-original-nft-id (asset-contract principal) (token-id uint))
(map-get? token-id-original-nft-id token-id)
)

(define-read-only (get-asset-token-id (asset-contract principal) (nft-token-id uint))
(map-get? nft-token-ids {asset-contract: asset-contract, nft-token-id: nft-token-id})
)

(define-public (get-or-create-asset-token-id (nft-token-id uint) (sip009-asset <sip009-transferable-trait>))
(match (get-asset-token-id (contract-of sip009-asset) nft-token-id)
token-id (ok token-id)
(let
(
(token-id (+ (var-get token-id-nonce) u1))
)
(asserts! (is-whitelisted (contract-of sip009-asset)) err-not-whitelisted)
(map-set nft-token-ids {asset-contract: (contract-of sip009-asset), nft-token-id: nft-token-id} token-id)
(map-set token-id-original-nft-id token-id nft-token-id)
(var-set token-id-nonce token-id)
(ok token-id)
)
)
)

(define-public (fractionalise (nft-token-id uint) (amount uint) (sip009-asset <sip009-transferable-trait>))
(let
(
(token-id (try! (get-or-create-asset-token-id nft-token-id sip009-asset)))
)
(try! (contract-call? sip009-asset transfer nft-token-id tx-sender (as-contract tx-sender)))
(try! (ft-mint? fractional-sip009-sft amount tx-sender))
(set-balance token-id amount tx-sender)
(map-set token-supplies token-id amount)
(print {type: "sft_mint_event", token-id: token-id, amount: amount, recipient: tx-sender})
(ok token-id)
)
)

(define-public (combine (nft-token-id uint) (recipient principal) (sip009-asset <sip009-transferable-trait>))
(let
(
(token-id (unwrap! (get-asset-token-id (contract-of sip009-asset) nft-token-id) err-unknown-token))
(token-supply (default-to u0 (map-get? token-supplies token-id)))
(original-sender tx-sender)
)
(asserts! (is-eq (get-balance-uint token-id tx-sender) token-supply) err-insufficient-balance)
(try! (ft-burn? fractional-sip009-sft token-supply original-sender))
(try! (as-contract (contract-call? sip009-asset transfer nft-token-id tx-sender original-sender)))
(set-balance token-id u0 original-sender)
(map-set token-supplies token-id u0)
(print {type: "sft_burn_event", token-id: token-id, amount: token-supply, sender: original-sender})
(ok token-id)
)
)

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

(define-public (set-whitelisted (asset-contract principal) (whitelisted bool))
(begin
(asserts! (is-eq contract-owner tx-sender) err-owner-only)
(ok (map-set asset-contract-whitelist asset-contract whitelisted))
)
)
File renamed without changes.
26 changes: 26 additions & 0 deletions tests/fractional-nft_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

import { Clarinet, Tx, Chain, Account, types } from 'https://deno.land/x/clarinet@v0.14.0/index.ts';
import { assertEquals } from 'https://deno.land/std@0.90.0/testing/asserts.ts';

Clarinet.test({
name: "Ensure that <...>",
async fn(chain: Chain, accounts: Map<string, Account>) {
let block = chain.mineBlock([
/*
* Add transactions with:
* Tx.contractCall(...)
*/
]);
assertEquals(block.receipts.length, 0);
assertEquals(block.height, 2);

block = chain.mineBlock([
/*
* Add transactions with:
* Tx.contractCall(...)
*/
]);
assertEquals(block.receipts.length, 0);
assertEquals(block.height, 3);
},
});
26 changes: 26 additions & 0 deletions tests/fractional-sip009-sft_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

import { Clarinet, Tx, Chain, Account, types } from 'https://deno.land/x/clarinet@v0.14.0/index.ts';
import { assertEquals } from 'https://deno.land/std@0.90.0/testing/asserts.ts';

Clarinet.test({
name: "Ensure that <...>",
async fn(chain: Chain, accounts: Map<string, Account>) {
let block = chain.mineBlock([
/*
* Add transactions with:
* Tx.contractCall(...)
*/
]);
assertEquals(block.receipts.length, 0);
assertEquals(block.height, 2);

block = chain.mineBlock([
/*
* Add transactions with:
* Tx.contractCall(...)
*/
]);
assertEquals(block.receipts.length, 0);
assertEquals(block.height, 3);
},
});

0 comments on commit 415bd05

Please sign in to comment.