diff --git a/contracts/Clarinet.toml b/contracts/Clarinet.toml
index d1b77a205..94ee715c6 100644
--- a/contracts/Clarinet.toml
+++ b/contracts/Clarinet.toml
@@ -25,6 +25,10 @@ path = 'contracts/sbtc-withdrawal.clar'
clarity_version = 2
epoch = 2.5
+[contracts.sbtc-token]
+path = 'contracts/sbtc-token.clar'
+clarity_version = 2
+epoch = 2.5
[repl.analysis]
passes = ['check_checker']
diff --git a/contracts/contracts/sbtc-deposit.clar b/contracts/contracts/sbtc-deposit.clar
index a69ecdd28..32cc590fe 100644
--- a/contracts/contracts/sbtc-deposit.clar
+++ b/contracts/contracts/sbtc-deposit.clar
@@ -4,6 +4,7 @@
;; The required length of a txid
(define-constant txid-length u32)
+(define-constant dust-limit u546)
;; error codes
@@ -11,6 +12,7 @@
(define-constant ERR_TXID_LEN (err u300))
;; Deposit has already been completed
(define-constant ERR_DEPOSIT_REPLAY (err u301))
+(define-constant ERR_LOWER_THAN_DUST (err u302))
;; data vars
@@ -32,14 +34,17 @@
;; TODO
;; Check that tx-sender is the bootstrap signer
+ ;; Check that amount is greater than dust limit
+ (asserts! (> amount dust-limit) ERR_LOWER_THAN_DUST)
+
;; Check that txid is the correct length
(asserts! (is-eq (len txid) txid-length) ERR_TXID_LEN)
;; Assert that the deposit has not already been completed (no replay)
(asserts! (is-none replay-fetch) ERR_DEPOSIT_REPLAY)
- ;; TODO
;; Mint the sBTC to the recipient
+ (try! (contract-call? .sbtc-token protocol-mint amount recipient))
;; Complete the deposit
(ok (contract-call? .sbtc-registry complete-deposit txid vout-index amount recipient))
diff --git a/contracts/contracts/sbtc-registry.clar b/contracts/contracts/sbtc-registry.clar
index da4170f70..1fe4c289e 100644
--- a/contracts/contracts/sbtc-registry.clar
+++ b/contracts/contracts/sbtc-registry.clar
@@ -59,6 +59,12 @@
;; stored to avoid replay
(define-map multi-sig-address principal bool)
+;; Data structure to store the active protocol contracts
+(define-map protocol-contracts principal bool)
+(map-set protocol-contracts .sbtc-bootstrap-signers true)
+(map-set protocol-contracts .sbtc-deposit true)
+(if (not is-in-mainnet) (map-set protocol-contracts tx-sender true) true)
+
;; Read-only functions
;; Get a withdrawal request by its ID.
;; This function returns the fields of the withrawal
@@ -171,7 +177,8 @@
(print {
topic: "completed-deposit",
txid: txid,
- vout-index: vout-index
+ vout-index: vout-index,
+ amount: amount
})
(ok true)
)
@@ -218,4 +225,12 @@
;; wont be hit
;; (if (is-eq contract-caller .controller) (ok true) (err ERR_UNAUTHORIZED))
(if false ERR_UNAUTHORIZED (ok true))
-)
\ No newline at end of file
+)
+
+;; Checks whether the contract-caller is a protocol contract
+(define-read-only (is-protocol-caller (principal-checked principal))
+ (is-some (map-get? protocol-contracts principal-checked))
+)
+
+;; TODO: Add a function to add a protocol contract
+;; TODO: Add a function to remove a protocol contract
\ No newline at end of file
diff --git a/contracts/contracts/sbtc-token.clar b/contracts/contracts/sbtc-token.clar
new file mode 100644
index 000000000..d13cdf50d
--- /dev/null
+++ b/contracts/contracts/sbtc-token.clar
@@ -0,0 +1,146 @@
+(define-constant ERR_NOT_OWNER (err u4)) ;; `tx-sender` or `contract-caller` tried to move a token it does not own.
+(define-constant ERR_NOT_AUTH (err u5)) ;; `tx-sender` or `contract-caller` is not the protocol caller
+
+(define-fungible-token sbtc-token)
+(define-fungible-token sbtc-token-locked)
+
+(define-data-var token-name (string-ascii 32) "sBTC Mini")
+(define-data-var token-symbol (string-ascii 10) "sBTC")
+(define-data-var token-uri (optional (string-utf8 256)) none)
+(define-constant token-decimals u8)
+
+(define-read-only (is-protocol-caller)
+ (ok (asserts! (contract-call? .sbtc-registry is-protocol-caller contract-caller) ERR_NOT_AUTH))
+)
+
+;; --- Protocol functions
+
+;; #[allow(unchecked_data)]
+(define-public (protocol-transfer (amount uint) (sender principal) (recipient principal))
+ (begin
+ (try! (is-protocol-caller))
+ (ft-transfer? sbtc-token amount sender recipient)
+ )
+)
+
+;; #[allow(unchecked_data)]
+(define-public (protocol-lock (amount uint) (owner principal))
+ (begin
+ (try! (is-protocol-caller))
+ (try! (ft-burn? sbtc-token amount owner))
+ (ft-mint? sbtc-token-locked amount owner)
+ )
+)
+
+;; #[allow(unchecked_data)]
+(define-public (protocol-unlock (amount uint) (owner principal))
+ (begin
+ (try! (is-protocol-caller))
+ (try! (ft-burn? sbtc-token-locked amount owner))
+ (ft-mint? sbtc-token amount owner)
+ )
+)
+
+;; #[allow(unchecked_data)]
+(define-public (protocol-mint (amount uint) (recipient principal))
+ (begin
+ (try! (is-protocol-caller))
+ (ft-mint? sbtc-token amount recipient)
+ )
+)
+
+;; #[allow(unchecked_data)]
+(define-public (protocol-burn (amount uint) (owner principal))
+ (begin
+ (try! (is-protocol-caller))
+ (ft-burn? sbtc-token amount owner)
+ )
+)
+
+;; #[allow(unchecked_data)]
+(define-public (protocol-burn-locked (amount uint) (owner principal))
+ (begin
+ (try! (is-protocol-caller))
+ (ft-burn? sbtc-token-locked amount owner)
+ )
+)
+
+;; #[allow(unchecked_data)]
+(define-public (protocol-set-name (new-name (string-ascii 32)))
+ (begin
+ (try! (is-protocol-caller))
+ (ok (var-set token-name new-name))
+ )
+)
+
+;; #[allow(unchecked_data)]
+(define-public (protocol-set-symbol (new-symbol (string-ascii 10)))
+ (begin
+ (try! (is-protocol-caller))
+ (ok (var-set token-symbol new-symbol))
+ )
+)
+
+;; #[allow(unchecked_data)]
+(define-public (protocol-set-token-uri (new-uri (optional (string-utf8 256))))
+ (begin
+ (try! (is-protocol-caller))
+ (ok (var-set token-uri new-uri))
+ )
+)
+
+(define-private (protocol-mint-many-iter (item {amount: uint, recipient: principal}))
+ (ft-mint? sbtc-token (get amount item) (get recipient item))
+)
+
+;; #[allow(unchecked_data)]
+(define-public (protocol-mint-many (recipients (list 200 {amount: uint, recipient: principal})))
+ (begin
+ (try! (is-protocol-caller))
+ (ok (map protocol-mint-many-iter recipients))
+ )
+)
+
+;; --- Public functions
+
+;; sip-010-trait
+
+;; #[allow(unchecked_data)]
+(define-public (transfer (amount uint) (sender principal) (recipient principal) (memo (optional (buff 34))))
+ (begin
+ (asserts! (or (is-eq tx-sender sender) (is-eq contract-caller sender)) ERR_NOT_OWNER)
+ (ft-transfer? sbtc-token amount sender recipient)
+ )
+)
+
+(define-read-only (get-name)
+ (ok (var-get token-name))
+)
+
+(define-read-only (get-symbol)
+ (ok (var-get token-symbol))
+)
+
+(define-read-only (get-decimals)
+ (ok token-decimals)
+)
+
+(define-read-only (get-balance (who principal))
+ (ok (+ (ft-get-balance sbtc-token who) (ft-get-balance sbtc-token-locked who)))
+)
+
+(define-read-only (get-balance-available (who principal))
+ (ok (ft-get-balance sbtc-token who))
+)
+
+(define-read-only (get-balance-locked (who principal))
+ (ok (ft-get-balance sbtc-token-locked who))
+)
+
+(define-read-only (get-total-supply)
+ (ok (+ (ft-get-supply sbtc-token) (ft-get-supply sbtc-token-locked)))
+)
+
+(define-read-only (get-token-uri)
+ (ok (var-get token-uri))
+)
\ No newline at end of file
diff --git a/contracts/deployments/default.simnet-plan.yaml b/contracts/deployments/default.simnet-plan.yaml
index d74b6c094..c4a9ef062 100644
--- a/contracts/deployments/default.simnet-plan.yaml
+++ b/contracts/deployments/default.simnet-plan.yaml
@@ -59,6 +59,11 @@ plan:
emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM
path: contracts/sbtc-bootstrap-signers.clar
clarity-version: 2
+ - emulated-contract-publish:
+ contract-name: sbtc-token
+ emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM
+ path: contracts/sbtc-token.clar
+ clarity-version: 2
- emulated-contract-publish:
contract-name: sbtc-deposit
emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM
diff --git a/contracts/docs/sbtc-deposit.md b/contracts/docs/sbtc-deposit.md
deleted file mode 100644
index 9afc1e3f9..000000000
--- a/contracts/docs/sbtc-deposit.md
+++ /dev/null
@@ -1,112 +0,0 @@
-# sbtc-deposit
-
-[`sbtc-deposit.clar`](../contracts/sbtc-deposit.clar)
-
-sBTC Deposit contract
-
-**Public functions:**
-
-- [`complete-deposit-wrapper`](#complete-deposit-wrapper)
-
-**Read-only functions:**
-
-**Private functions:**
-
-**Maps**
-
-**Variables**
-
-**Constants**
-
-- [`txid-length`](#txid-length)
-- [`ERR_TXID_LEN`](#err_txid_len)
-- [`ERR_DEPOSIT_REPLAY`](#err_deposit_replay)
-
-## Functions
-
-### complete-deposit-wrapper
-
-[View in file](../contracts/sbtc-deposit.clar#L26)
-
-`(define-public (complete-deposit-wrapper ((txid (buff 32)) (vout-index uint) (amount uint) (recipient principal)) (response (response bool uint) uint))`
-
-Accept a new deposit request
-Note that this function can only be called by the current
-bootstrap signer set address - it cannot be called by users directly.
-This function handles the validation & minting of sBTC, it then calls
-into the sbtc-registry contract to update the state of the protocol
-
-
- Source code:
-
-```clarity
-(define-public (complete-deposit-wrapper (txid (buff 32)) (vout-index uint) (amount uint) (recipient principal))
- (let
- (
- (replay-fetch (contract-call? .sbtc-registry get-completed-deposit txid vout-index))
- )
-
- ;; TODO
- ;; Check that tx-sender is the bootstrap signer
-
- ;; Check that txid is the correct length
- (asserts! (is-eq (len txid) txid-length) ERR_TXID_LEN)
-
- ;; Assert that the deposit has not already been completed (no replay)
- (asserts! (is-none replay-fetch) ERR_DEPOSIT_REPLAY)
-
- ;; TODO
- ;; Mint the sBTC to the recipient
-
- ;; Complete the deposit
- (ok (contract-call? .sbtc-registry complete-deposit txid vout-index amount recipient))
- )
-)
-```
-
-
-
-**Parameters:**
-
-| Name | Type |
-| ---------- | --------- |
-| txid | (buff 32) |
-| vout-index | uint |
-| amount | uint |
-| recipient | principal |
-
-## Maps
-
-## Variables
-
-## Constants
-
-### txid-length
-
-The required length of a txid
-
-```clarity
-(define-constant txid-length u32)
-```
-
-[View in file](../contracts/sbtc-deposit.clar#L6)
-
-### ERR_TXID_LEN
-
-TXID used in deposit is not the correct length
-
-```clarity
-(define-constant ERR_TXID_LEN (err u300))
-```
-
-[View in file](../contracts/sbtc-deposit.clar#L11)
-
-### ERR_DEPOSIT_REPLAY
-
-Deposit has already been completed
-
-```clarity
-(define-constant ERR_DEPOSIT_REPLAY (err u301))
-```
-
-[View in file](../contracts/sbtc-deposit.clar#L13)
diff --git a/contracts/docs/sbtc-registry.md b/contracts/docs/sbtc-registry.md
deleted file mode 100644
index 7db74f82f..000000000
--- a/contracts/docs/sbtc-registry.md
+++ /dev/null
@@ -1,554 +0,0 @@
-# sbtc-registry
-
-[`sbtc-registry.clar`](../contracts/sbtc-registry.clar)
-
-sBTC Registry contract
-
-**Public functions:**
-
-- [`create-withdrawal-request`](#create-withdrawal-request)
-- [`complete-deposit`](#complete-deposit)
-- [`rotate-keys`](#rotate-keys)
-
-**Read-only functions:**
-
-- [`get-withdrawal-request`](#get-withdrawal-request)
-- [`get-completed-deposit`](#get-completed-deposit)
-- [`get-current-signer-data`](#get-current-signer-data)
-- [`get-current-aggregate-pubkey`](#get-current-aggregate-pubkey)
-- [`get-current-signer-principal`](#get-current-signer-principal)
-- [`get-current-signer-set`](#get-current-signer-set)
-
-**Private functions:**
-
-- [`increment-last-withdrawal-request-id`](#increment-last-withdrawal-request-id)
-- [`validate-caller`](#validate-caller)
-
-**Maps**
-
-- [`withdrawal-requests`](#withdrawal-requests)
-- [`withdrawal-status`](#withdrawal-status)
-- [`completed-deposits`](#completed-deposits)
-- [`aggregate-pubkeys`](#aggregate-pubkeys)
-- [`multi-sig-address`](#multi-sig-address)
-
-**Variables**
-
-- [`last-withdrawal-request-id`](#last-withdrawal-request-id)
-- [`current-signer-set`](#current-signer-set)
-- [`current-aggregate-pubkey`](#current-aggregate-pubkey)
-- [`current-signer-principal`](#current-signer-principal)
-
-**Constants**
-
-- [`ERR_UNAUTHORIZED`](#err_unauthorized)
-- [`ERR_INVALID_REQUEST_ID`](#err_invalid_request_id)
-- [`ERR_AGG_PUBKEY_REPLAY`](#err_agg_pubkey_replay)
-- [`ERR_MULTI_SIG_REPLAY`](#err_multi_sig_replay)
-
-## Functions
-
-### get-withdrawal-request
-
-[View in file](../contracts/sbtc-registry.clar#L66)
-
-`(define-read-only (get-withdrawal-request ((id uint)) (optional (tuple (amount uint) (block-height uint) (max-fee uint) (recipient (tuple (hashbytes (buff 32)) (version (buff 1)))) (sender principal) (status (optional bool)))))`
-
-Read-only functions
-Get a withdrawal request by its ID.
-This function returns the fields of the withrawal
-request, along with its status.
-
-
- Source code:
-
-```clarity
-(define-read-only (get-withdrawal-request (id uint))
- (match (map-get? withdrawal-requests id)
- request (some (merge request {
- status: (map-get? withdrawal-status id)
- }))
- none
- )
-)
-```
-
-
-
-**Parameters:**
-
-| Name | Type |
-| ---- | ---- |
-| id | uint |
-
-### get-completed-deposit
-
-[View in file](../contracts/sbtc-registry.clar#L77)
-
-`(define-read-only (get-completed-deposit ((txid (buff 32)) (vout-index uint)) (optional (tuple (amount uint) (recipient principal))))`
-
-Get a completed deposit by its transaction ID & vout index.
-This function returns the fields of the completed-deposits map.
-
-
- Source code:
-
-```clarity
-(define-read-only (get-completed-deposit (txid (buff 32)) (vout-index uint))
- (map-get? completed-deposits {txid: txid, vout-index: vout-index})
-)
-```
-
-
-
-**Parameters:**
-
-| Name | Type |
-| ---------- | --------- |
-| txid | (buff 32) |
-| vout-index | uint |
-
-### get-current-signer-data
-
-[View in file](../contracts/sbtc-registry.clar#L83)
-
-`(define-read-only (get-current-signer-data () (tuple (current-aggregate-pubkey (buff 33)) (current-signer-principal principal) (current-signer-set (list 15 (buff 33)))))`
-
-Get the current signer set.
-This function returns the current signer set as a list of principals.
-
-
- Source code:
-
-```clarity
-(define-read-only (get-current-signer-data)
- {
- current-signer-set: (var-get current-signer-set),
- current-aggregate-pubkey: (var-get current-aggregate-pubkey),
- current-signer-principal: (var-get current-signer-principal)
- }
-)
-```
-
-
-
-### get-current-aggregate-pubkey
-
-[View in file](../contracts/sbtc-registry.clar#L93)
-
-`(define-read-only (get-current-aggregate-pubkey () (buff 33))`
-
-Get the current aggregate pubkey.
-This function returns the current aggregate pubkey.
-
-
- Source code:
-
-```clarity
-(define-read-only (get-current-aggregate-pubkey)
- (var-get current-aggregate-pubkey)
-)
-```
-
-
-
-### get-current-signer-principal
-
-[View in file](../contracts/sbtc-registry.clar#L99)
-
-`(define-read-only (get-current-signer-principal () principal)`
-
-Get the current signer principal.
-This function returns the current signer principal.
-
-
- Source code:
-
-```clarity
-(define-read-only (get-current-signer-principal)
- (var-get current-signer-principal)
-)
-```
-
-
-
-### get-current-signer-set
-
-[View in file](../contracts/sbtc-registry.clar#L103)
-
-`(define-read-only (get-current-signer-set () (list 15 (buff 33)))`
-
-
- Source code:
-
-```clarity
-(define-read-only (get-current-signer-set)
- (var-get current-signer-set)
-)
-```
-
-
-
-### create-withdrawal-request
-
-[View in file](../contracts/sbtc-registry.clar#L119)
-
-`(define-public (create-withdrawal-request ((amount uint) (max-fee uint) (sender principal) (recipient (tuple (hashbytes (buff 32)) (version (buff 1)))) (height uint)) (response uint uint))`
-
-Store a new withdrawal request.
-Note that this function can only be called by other sBTC
-contracts - it cannot be called by users directly.
-
-This function does not handle validation or moving the funds.
-Instead, it is purely for the purpose of storing the request.
-
-The function will emit a print event with the topic "withdrawal-request"
-and the data of the request.
-
-
- Source code:
-
-```clarity
-(define-public (create-withdrawal-request
- (amount uint)
- (max-fee uint)
- (sender principal)
- (recipient { version: (buff 1), hashbytes: (buff 32) })
- (height uint)
- )
- (let
- (
- (id (increment-last-withdrawal-request-id))
- )
- (try! (validate-caller))
- ;; #[allow(unchecked_data)]
- (map-insert withdrawal-requests id {
- amount: amount,
- max-fee: max-fee,
- sender: sender,
- recipient: recipient,
- block-height: height,
- })
- (print {
- topic: "withdrawal-request",
- amount: amount,
- request-id: id,
- sender: sender,
- recipient: recipient,
- block-height: height,
- max-fee: max-fee,
- })
- (ok id)
- )
-)
-```
-
-
-
-**Parameters:**
-
-| Name | Type |
-| --------- | ------------------------------------------------ |
-| amount | uint |
-| max-fee | uint |
-| sender | principal |
-| recipient | (tuple (hashbytes (buff 32)) (version (buff 1))) |
-| height | uint |
-
-### complete-deposit
-
-[View in file](../contracts/sbtc-registry.clar#L159)
-
-`(define-public (complete-deposit ((txid (buff 32)) (vout-index uint) (amount uint) (recipient principal)) (response bool uint))`
-
-Store a new insert request.
-Note that this function can only be called by other sBTC
-contracts (specifically the current version of the deposit contract)
-
-- it cannot be called by users directly.
-
-This function does not handle validation or moving the funds.
-Instead, it is purely for the purpose of storing the completed deposit.
-
-
- Source code:
-
-```clarity
-(define-public (complete-deposit
- (txid (buff 32))
- (vout-index uint)
- (amount uint)
- (recipient principal)
- )
- (begin
- (try! (validate-caller))
- (map-insert completed-deposits {txid: txid, vout-index: vout-index} {
- amount: amount,
- recipient: recipient
- })
- (print {
- topic: "completed-deposit",
- txid: txid,
- vout-index: vout-index
- })
- (ok true)
- )
-)
-```
-
-
-
-**Parameters:**
-
-| Name | Type |
-| ---------- | --------- |
-| txid | (buff 32) |
-| vout-index | uint |
-| amount | uint |
-| recipient | principal |
-
-### rotate-keys
-
-[View in file](../contracts/sbtc-registry.clar#L182)
-
-`(define-public (rotate-keys ((new-keys (list 15 (buff 33))) (new-address principal) (new-aggregate-pubkey (buff 33))) (response bool uint))`
-
-Rotate the signer set, multi-sig principal, & aggregate pubkey
-This function can only be called by the bootstrap-signers contract.
-
-
- Source code:
-
-```clarity
-(define-public (rotate-keys (new-keys (list 15 (buff 33))) (new-address principal) (new-aggregate-pubkey (buff 33)))
- (begin
- ;; Check that caller is protocol contract
- (try! (validate-caller))
- ;; Check that the aggregate pubkey is not already in the map
- (asserts! (map-insert aggregate-pubkeys new-aggregate-pubkey true) ERR_AGG_PUBKEY_REPLAY)
- ;; Check that the new address (multi-sig) is not already in the map
- (asserts! (map-insert multi-sig-address new-address true) ERR_MULTI_SIG_REPLAY)
- ;; Update the current signer set
- (var-set current-signer-set new-keys)
- ;; Update the current multi-sig address
- (var-set current-signer-principal new-address)
- ;; Update the current aggregate pubkey
- (ok (var-set current-aggregate-pubkey new-aggregate-pubkey))
- )
-)
-```
-
-
-
-**Parameters:**
-
-| Name | Type |
-| -------------------- | ------------------- |
-| new-keys | (list 15 (buff 33)) |
-| new-address | principal |
-| new-aggregate-pubkey | (buff 33) |
-
-### increment-last-withdrawal-request-id
-
-[View in file](../contracts/sbtc-registry.clar#L203)
-
-`(define-private (increment-last-withdrawal-request-id () uint)`
-
-Increment the last withdrawal request ID and
-return the new value.
-
-
- Source code:
-
-```clarity
-(define-private (increment-last-withdrawal-request-id)
- (let
- (
- (next-value (+ u1 (var-get last-withdrawal-request-id)))
- )
- (var-set last-withdrawal-request-id next-value)
- next-value
- )
-)
-```
-
-
-
-### validate-caller
-
-[View in file](../contracts/sbtc-registry.clar#L216)
-
-`(define-private (validate-caller () (response bool uint))`
-
-Validate the caller of the function.
-TODO: Once other contracts are in place, update this
-to use the sBTC controller.
-
-
- Source code:
-
-```clarity
-(define-private (validate-caller)
- ;; To provide an explicit error type, add a branch that
- ;; wont be hit
- ;; (if (is-eq contract-caller .controller) (ok true) (err ERR_UNAUTHORIZED))
- (if false ERR_UNAUTHORIZED (ok true))
-)
-```
-
-
-
-## Maps
-
-### withdrawal-requests
-
-Internal data structure to store withdrawal
-requests. Requests are associated with a unique
-request ID.
-
-```clarity
-(define-map withdrawal-requests uint {
- ;; Amount of sBTC being withdrawaled (in sats)
- amount: uint,
- max-fee: uint,
- sender: principal,
- ;; BTC recipient address in the same format of
- ;; pox contracts
- recipient: {
- version: (buff 1),
- hashbytes: (buff 32),
- },
- ;; Burn block height where the withdrawal request was
- ;; created
- block-height: uint,
-})
-```
-
-[View in file](../contracts/sbtc-registry.clar#L23)
-
-### withdrawal-status
-
-Data structure to map request-id to status
-If status is `none`, the request is pending.
-Otherwise, the boolean value indicates whether
-the deposit was accepted.
-
-```clarity
-(define-map withdrawal-status uint bool)
-```
-
-[View in file](../contracts/sbtc-registry.clar#L43)
-
-### completed-deposits
-
-Internal data structure to store completed
-deposit requests & avoid replay attacks.
-
-```clarity
-(define-map completed-deposits {txid: (buff 32), vout-index: uint}
- {
- amount: uint,
- recipient: principal
- }
-)
-```
-
-[View in file](../contracts/sbtc-registry.clar#L47)
-
-### aggregate-pubkeys
-
-Data structure to store aggregate pubkey,
-stored to avoid replay
-
-```clarity
-(define-map aggregate-pubkeys (buff 33) bool)
-```
-
-[View in file](../contracts/sbtc-registry.clar#L56)
-
-### multi-sig-address
-
-Data structure to store the current signer set,
-stored to avoid replay
-
-```clarity
-(define-map multi-sig-address principal bool)
-```
-
-[View in file](../contracts/sbtc-registry.clar#L60)
-
-## Variables
-
-### last-withdrawal-request-id
-
-uint
-
-```clarity
-(define-data-var last-withdrawal-request-id uint u0)
-```
-
-[View in file](../contracts/sbtc-registry.clar#L12)
-
-### current-signer-set
-
-(list 15 (buff 33))
-
-```clarity
-(define-data-var current-signer-set (list 15 (buff 33)) (list))
-```
-
-[View in file](../contracts/sbtc-registry.clar#L13)
-
-### current-aggregate-pubkey
-
-(buff 33)
-
-```clarity
-(define-data-var current-aggregate-pubkey (buff 33) 0x00)
-```
-
-[View in file](../contracts/sbtc-registry.clar#L14)
-
-### current-signer-principal
-
-principal
-
-```clarity
-(define-data-var current-signer-principal principal tx-sender)
-```
-
-[View in file](../contracts/sbtc-registry.clar#L15)
-
-## Constants
-
-### ERR_UNAUTHORIZED
-
-```clarity
-(define-constant ERR_UNAUTHORIZED (err u400))
-```
-
-[View in file](../contracts/sbtc-registry.clar#L5)
-
-### ERR_INVALID_REQUEST_ID
-
-```clarity
-(define-constant ERR_INVALID_REQUEST_ID (err u401))
-```
-
-[View in file](../contracts/sbtc-registry.clar#L6)
-
-### ERR_AGG_PUBKEY_REPLAY
-
-```clarity
-(define-constant ERR_AGG_PUBKEY_REPLAY (err u402))
-```
-
-[View in file](../contracts/sbtc-registry.clar#L7)
-
-### ERR_MULTI_SIG_REPLAY
-
-```clarity
-(define-constant ERR_MULTI_SIG_REPLAY (err u403))
-```
-
-[View in file](../contracts/sbtc-registry.clar#L8)
diff --git a/contracts/tests/clarigen-types.ts b/contracts/tests/clarigen-types.ts
index fdc0c09ad..95e1629e4 100644
--- a/contracts/tests/clarigen-types.ts
+++ b/contracts/tests/clarigen-types.ts
@@ -517,6 +517,16 @@ export const contracts = {
},
access: "constant",
} as TypedAbiVariable>,
+ ERR_LOWER_THAN_DUST: {
+ name: "ERR_LOWER_THAN_DUST",
+ type: {
+ response: {
+ ok: "none",
+ error: "uint128",
+ },
+ },
+ access: "constant",
+ } as TypedAbiVariable>,
ERR_TXID_LEN: {
name: "ERR_TXID_LEN",
type: {
@@ -527,6 +537,11 @@ export const contracts = {
},
access: "constant",
} as TypedAbiVariable>,
+ dustLimit: {
+ name: "dust-limit",
+ type: "uint128",
+ access: "constant",
+ } as TypedAbiVariable,
txidLength: {
name: "txid-length",
type: "uint128",
@@ -538,10 +553,15 @@ export const contracts = {
isOk: false,
value: 301n,
},
+ ERR_LOWER_THAN_DUST: {
+ isOk: false,
+ value: 302n,
+ },
ERR_TXID_LEN: {
isOk: false,
value: 300n,
},
+ dustLimit: 546n,
txidLength: 32n,
},
non_fungible_tokens: [],
@@ -754,6 +774,15 @@ export const contracts = {
status: boolean | null;
} | null
>,
+ isProtocolCaller: {
+ name: "is-protocol-caller",
+ access: "read_only",
+ args: [{ name: "principal-checked", type: "principal" }],
+ outputs: { type: "bool" },
+ } as TypedAbiFunction<
+ [principalChecked: TypedAbiArg],
+ boolean
+ >,
},
maps: {
aggregatePubkeys: {
@@ -790,6 +819,11 @@ export const contracts = {
key: "principal",
value: "bool",
} as TypedAbiMap,
+ protocolContracts: {
+ name: "protocol-contracts",
+ key: "principal",
+ value: "bool",
+ } as TypedAbiMap,
withdrawalRequests: {
name: "withdrawal-requests",
key: "uint128",
@@ -932,6 +966,380 @@ export const contracts = {
clarity_version: "Clarity2",
contractName: "sbtc-registry",
},
+ sbtcToken: {
+ functions: {
+ protocolMintManyIter: {
+ name: "protocol-mint-many-iter",
+ access: "private",
+ args: [
+ {
+ name: "item",
+ type: {
+ tuple: [
+ { name: "amount", type: "uint128" },
+ { name: "recipient", type: "principal" },
+ ],
+ },
+ },
+ ],
+ outputs: { type: { response: { ok: "bool", error: "uint128" } } },
+ } as TypedAbiFunction<
+ [
+ item: TypedAbiArg<
+ {
+ amount: number | bigint;
+ recipient: string;
+ },
+ "item"
+ >,
+ ],
+ Response
+ >,
+ protocolBurn: {
+ name: "protocol-burn",
+ access: "public",
+ args: [
+ { name: "amount", type: "uint128" },
+ { name: "owner", type: "principal" },
+ ],
+ outputs: { type: { response: { ok: "bool", error: "uint128" } } },
+ } as TypedAbiFunction<
+ [
+ amount: TypedAbiArg,
+ owner: TypedAbiArg,
+ ],
+ Response
+ >,
+ protocolBurnLocked: {
+ name: "protocol-burn-locked",
+ access: "public",
+ args: [
+ { name: "amount", type: "uint128" },
+ { name: "owner", type: "principal" },
+ ],
+ outputs: { type: { response: { ok: "bool", error: "uint128" } } },
+ } as TypedAbiFunction<
+ [
+ amount: TypedAbiArg,
+ owner: TypedAbiArg,
+ ],
+ Response
+ >,
+ protocolLock: {
+ name: "protocol-lock",
+ access: "public",
+ args: [
+ { name: "amount", type: "uint128" },
+ { name: "owner", type: "principal" },
+ ],
+ outputs: { type: { response: { ok: "bool", error: "uint128" } } },
+ } as TypedAbiFunction<
+ [
+ amount: TypedAbiArg,
+ owner: TypedAbiArg,
+ ],
+ Response
+ >,
+ protocolMint: {
+ name: "protocol-mint",
+ access: "public",
+ args: [
+ { name: "amount", type: "uint128" },
+ { name: "recipient", type: "principal" },
+ ],
+ outputs: { type: { response: { ok: "bool", error: "uint128" } } },
+ } as TypedAbiFunction<
+ [
+ amount: TypedAbiArg,
+ recipient: TypedAbiArg,
+ ],
+ Response
+ >,
+ protocolMintMany: {
+ name: "protocol-mint-many",
+ access: "public",
+ args: [
+ {
+ name: "recipients",
+ type: {
+ list: {
+ type: {
+ tuple: [
+ { name: "amount", type: "uint128" },
+ { name: "recipient", type: "principal" },
+ ],
+ },
+ length: 200,
+ },
+ },
+ },
+ ],
+ outputs: {
+ type: {
+ response: {
+ ok: {
+ list: {
+ type: { response: { ok: "bool", error: "uint128" } },
+ length: 200,
+ },
+ },
+ error: "uint128",
+ },
+ },
+ },
+ } as TypedAbiFunction<
+ [
+ recipients: TypedAbiArg<
+ {
+ amount: number | bigint;
+ recipient: string;
+ }[],
+ "recipients"
+ >,
+ ],
+ Response[], bigint>
+ >,
+ protocolSetName: {
+ name: "protocol-set-name",
+ access: "public",
+ args: [{ name: "new-name", type: { "string-ascii": { length: 32 } } }],
+ outputs: { type: { response: { ok: "bool", error: "uint128" } } },
+ } as TypedAbiFunction<
+ [newName: TypedAbiArg],
+ Response
+ >,
+ protocolSetSymbol: {
+ name: "protocol-set-symbol",
+ access: "public",
+ args: [
+ { name: "new-symbol", type: { "string-ascii": { length: 10 } } },
+ ],
+ outputs: { type: { response: { ok: "bool", error: "uint128" } } },
+ } as TypedAbiFunction<
+ [newSymbol: TypedAbiArg],
+ Response
+ >,
+ protocolSetTokenUri: {
+ name: "protocol-set-token-uri",
+ access: "public",
+ args: [
+ {
+ name: "new-uri",
+ type: { optional: { "string-utf8": { length: 256 } } },
+ },
+ ],
+ outputs: { type: { response: { ok: "bool", error: "uint128" } } },
+ } as TypedAbiFunction<
+ [newUri: TypedAbiArg],
+ Response
+ >,
+ protocolTransfer: {
+ name: "protocol-transfer",
+ access: "public",
+ args: [
+ { name: "amount", type: "uint128" },
+ { name: "sender", type: "principal" },
+ { name: "recipient", type: "principal" },
+ ],
+ outputs: { type: { response: { ok: "bool", error: "uint128" } } },
+ } as TypedAbiFunction<
+ [
+ amount: TypedAbiArg,
+ sender: TypedAbiArg,
+ recipient: TypedAbiArg,
+ ],
+ Response
+ >,
+ protocolUnlock: {
+ name: "protocol-unlock",
+ access: "public",
+ args: [
+ { name: "amount", type: "uint128" },
+ { name: "owner", type: "principal" },
+ ],
+ outputs: { type: { response: { ok: "bool", error: "uint128" } } },
+ } as TypedAbiFunction<
+ [
+ amount: TypedAbiArg,
+ owner: TypedAbiArg,
+ ],
+ Response
+ >,
+ transfer: {
+ name: "transfer",
+ access: "public",
+ args: [
+ { name: "amount", type: "uint128" },
+ { name: "sender", type: "principal" },
+ { name: "recipient", type: "principal" },
+ { name: "memo", type: { optional: { buffer: { length: 34 } } } },
+ ],
+ outputs: { type: { response: { ok: "bool", error: "uint128" } } },
+ } as TypedAbiFunction<
+ [
+ amount: TypedAbiArg,
+ sender: TypedAbiArg,
+ recipient: TypedAbiArg,
+ memo: TypedAbiArg,
+ ],
+ Response
+ >,
+ getBalance: {
+ name: "get-balance",
+ access: "read_only",
+ args: [{ name: "who", type: "principal" }],
+ outputs: { type: { response: { ok: "uint128", error: "none" } } },
+ } as TypedAbiFunction<
+ [who: TypedAbiArg],
+ Response
+ >,
+ getBalanceAvailable: {
+ name: "get-balance-available",
+ access: "read_only",
+ args: [{ name: "who", type: "principal" }],
+ outputs: { type: { response: { ok: "uint128", error: "none" } } },
+ } as TypedAbiFunction<
+ [who: TypedAbiArg],
+ Response
+ >,
+ getBalanceLocked: {
+ name: "get-balance-locked",
+ access: "read_only",
+ args: [{ name: "who", type: "principal" }],
+ outputs: { type: { response: { ok: "uint128", error: "none" } } },
+ } as TypedAbiFunction<
+ [who: TypedAbiArg],
+ Response
+ >,
+ getDecimals: {
+ name: "get-decimals",
+ access: "read_only",
+ args: [],
+ outputs: { type: { response: { ok: "uint128", error: "none" } } },
+ } as TypedAbiFunction<[], Response>,
+ getName: {
+ name: "get-name",
+ access: "read_only",
+ args: [],
+ outputs: {
+ type: {
+ response: { ok: { "string-ascii": { length: 32 } }, error: "none" },
+ },
+ },
+ } as TypedAbiFunction<[], Response>,
+ getSymbol: {
+ name: "get-symbol",
+ access: "read_only",
+ args: [],
+ outputs: {
+ type: {
+ response: { ok: { "string-ascii": { length: 10 } }, error: "none" },
+ },
+ },
+ } as TypedAbiFunction<[], Response>,
+ getTokenUri: {
+ name: "get-token-uri",
+ access: "read_only",
+ args: [],
+ outputs: {
+ type: {
+ response: {
+ ok: { optional: { "string-utf8": { length: 256 } } },
+ error: "none",
+ },
+ },
+ },
+ } as TypedAbiFunction<[], Response>,
+ getTotalSupply: {
+ name: "get-total-supply",
+ access: "read_only",
+ args: [],
+ outputs: { type: { response: { ok: "uint128", error: "none" } } },
+ } as TypedAbiFunction<[], Response>,
+ isProtocolCaller: {
+ name: "is-protocol-caller",
+ access: "read_only",
+ args: [],
+ outputs: { type: { response: { ok: "bool", error: "uint128" } } },
+ } as TypedAbiFunction<[], Response>,
+ },
+ maps: {},
+ variables: {
+ ERR_NOT_AUTH: {
+ name: "ERR_NOT_AUTH",
+ type: {
+ response: {
+ ok: "none",
+ error: "uint128",
+ },
+ },
+ access: "constant",
+ } as TypedAbiVariable>,
+ ERR_NOT_OWNER: {
+ name: "ERR_NOT_OWNER",
+ type: {
+ response: {
+ ok: "none",
+ error: "uint128",
+ },
+ },
+ access: "constant",
+ } as TypedAbiVariable>,
+ tokenDecimals: {
+ name: "token-decimals",
+ type: "uint128",
+ access: "constant",
+ } as TypedAbiVariable,
+ tokenName: {
+ name: "token-name",
+ type: {
+ "string-ascii": {
+ length: 32,
+ },
+ },
+ access: "variable",
+ } as TypedAbiVariable,
+ tokenSymbol: {
+ name: "token-symbol",
+ type: {
+ "string-ascii": {
+ length: 10,
+ },
+ },
+ access: "variable",
+ } as TypedAbiVariable,
+ tokenUri: {
+ name: "token-uri",
+ type: {
+ optional: {
+ "string-utf8": {
+ length: 256,
+ },
+ },
+ },
+ access: "variable",
+ } as TypedAbiVariable,
+ },
+ constants: {
+ ERR_NOT_AUTH: {
+ isOk: false,
+ value: 5n,
+ },
+ ERR_NOT_OWNER: {
+ isOk: false,
+ value: 4n,
+ },
+ tokenDecimals: 8n,
+ tokenName: "sBTC Mini",
+ tokenSymbol: "sBTC",
+ tokenUri: null,
+ },
+ non_fungible_tokens: [],
+ fungible_tokens: [{ name: "sbtc-token" }, { name: "sbtc-token-locked" }],
+ epoch: "Epoch25",
+ clarity_version: "Clarity2",
+ contractName: "sbtc-token",
+ },
sbtcWithdrawal: {
functions: {
initiateWithdrawalRequest: {
@@ -1100,6 +1508,7 @@ export const identifiers = {
"ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.sbtc-bootstrap-signers",
sbtcDeposit: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.sbtc-deposit",
sbtcRegistry: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.sbtc-registry",
+ sbtcToken: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.sbtc-token",
sbtcWithdrawal: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.sbtc-withdrawal",
} as const;
@@ -1128,6 +1537,12 @@ export const deployments = {
testnet: null,
mainnet: null,
},
+ sbtcToken: {
+ devnet: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.sbtc-token",
+ simnet: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.sbtc-token",
+ testnet: null,
+ mainnet: null,
+ },
sbtcWithdrawal: {
devnet: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.sbtc-withdrawal",
simnet: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.sbtc-withdrawal",
diff --git a/contracts/tests/helpers.ts b/contracts/tests/helpers.ts
index 57ccd7135..2b706be7b 100644
--- a/contracts/tests/helpers.ts
+++ b/contracts/tests/helpers.ts
@@ -26,6 +26,7 @@ export const registry = contracts.sbtcRegistry;
export const deposit = contracts.sbtcDeposit;
export const signers = contracts.sbtcBootstrapSigners;
export const withdrawal = contracts.sbtcWithdrawal;
+export const token = contracts.sbtcToken;
export const controllerId = `${accounts.deployer.address}.controller`;
@@ -36,6 +37,7 @@ export const errors = {
deposit: _errors.sbtcDeposit,
signers: _errors.sbtcBootstrapSigners,
withdrawal: _errors.sbtcWithdrawal,
+ token: _errors.sbtcToken,
};
export function getLastWithdrawalRequestId() {
diff --git a/contracts/tests/sbtc-deposit.test.ts b/contracts/tests/sbtc-deposit.test.ts
index 7aaa7fbcf..84936c348 100644
--- a/contracts/tests/sbtc-deposit.test.ts
+++ b/contracts/tests/sbtc-deposit.test.ts
@@ -10,7 +10,7 @@ describe("sBTC deposit contract", () => {
deposit.completeDepositWrapper({
txid: new Uint8Array(31).fill(0),
voutIndex: 0,
- amount: 0,
+ amount: 1000n,
recipient: alice,
}),
alice
@@ -18,12 +18,25 @@ describe("sBTC deposit contract", () => {
expect(receipt.value).toEqual(errors.deposit.ERR_TXID_LEN);
});
+ test("Fail complete-deposit-wrapper invalid low amount", () => {
+ const receipt = txErr(
+ deposit.completeDepositWrapper({
+ txid: new Uint8Array(32).fill(0),
+ voutIndex: 0,
+ amount: 10n,
+ recipient: alice,
+ }),
+ alice
+ );
+ expect(receipt.value).toEqual(deposit.constants.ERR_LOWER_THAN_DUST.value);
+ });
+
test("Fail complete-deposit-wrapper replay deposit (err 301)", () => {
const receipt0 = txOk(
deposit.completeDepositWrapper({
txid: new Uint8Array(32).fill(0),
voutIndex: 0,
- amount: 0,
+ amount: 1000n,
recipient: alice,
}),
alice
@@ -33,7 +46,7 @@ describe("sBTC deposit contract", () => {
deposit.completeDepositWrapper({
txid: new Uint8Array(32).fill(0),
voutIndex: 0,
- amount: 0,
+ amount: 1000n,
recipient: alice,
}),
alice
@@ -46,7 +59,7 @@ describe("sBTC deposit contract", () => {
deposit.completeDepositWrapper({
txid: new Uint8Array(32).fill(0),
voutIndex: 0,
- amount: 0,
+ amount: 1000n,
recipient: alice,
}),
alice
@@ -60,11 +73,13 @@ describe("sBTC deposit contract", () => {
topic: string;
txid: string;
voutIndex: bigint;
+ amount: bigint;
}>(print.data.value);
expect(printData).toStrictEqual({
topic: "completed-deposit",
txid: new Uint8Array(32).fill(0),
voutIndex: 0n,
+ amount: 1000n,
});
});
@@ -73,7 +88,7 @@ describe("sBTC deposit contract", () => {
deposit.completeDepositWrapper({
txid: new Uint8Array(32).fill(0),
voutIndex: 0,
- amount: 0,
+ amount: 1000n,
recipient: alice,
}),
alice
@@ -86,7 +101,7 @@ describe("sBTC deposit contract", () => {
alice
);
expect(receipt1).toStrictEqual({
- amount: 0n,
+ amount: 1000n,
recipient: alice,
});
});
diff --git a/contracts/tests/sbtc-token.test.ts b/contracts/tests/sbtc-token.test.ts
new file mode 100644
index 000000000..b88c9b7e5
--- /dev/null
+++ b/contracts/tests/sbtc-token.test.ts
@@ -0,0 +1,114 @@
+import { alice, bob, deposit, errors, token } from "./helpers";
+import { test, expect, describe } from "vitest";
+import { txOk, filterEvents, rov, txErr } from "@clarigen/test";
+import { CoreNodeEventType, cvToValue } from "@clarigen/core";
+
+describe("sBTC token contract", () => {
+ describe("token basics", () => {
+ test("Mint sbtc token, check Alice balance", () => {
+ const receipt = txOk(
+ deposit.completeDepositWrapper({
+ txid: new Uint8Array(32).fill(0),
+ voutIndex: 0,
+ amount: 1000n,
+ recipient: alice,
+ }),
+ alice
+ );
+ const printEvents = filterEvents(
+ receipt.events,
+ CoreNodeEventType.ContractEvent
+ );
+ const [print] = printEvents;
+ const printData = cvToValue<{
+ topic: string;
+ txid: string;
+ voutIndex: bigint;
+ amount: bigint;
+ }>(print.data.value);
+ expect(printData).toStrictEqual({
+ topic: "completed-deposit",
+ txid: new Uint8Array(32).fill(0),
+ voutIndex: 0n,
+ amount: 1000n,
+ });
+ const receipt1 = rov(
+ token.getBalance({
+ who: alice,
+ }),
+ alice
+ );
+ expect(receipt1.value).toEqual(1000n);
+ });
+
+ test("Mint & transfer sbtc token, check Bob balance", () => {
+ const receipt = txOk(
+ deposit.completeDepositWrapper({
+ txid: new Uint8Array(32).fill(0),
+ voutIndex: 0,
+ amount: 1000n,
+ recipient: alice,
+ }),
+ alice
+ );
+ const printEvents = filterEvents(
+ receipt.events,
+ CoreNodeEventType.ContractEvent
+ );
+ const [print] = printEvents;
+ const printData = cvToValue<{
+ topic: string;
+ txid: string;
+ voutIndex: bigint;
+ amount: bigint;
+ }>(print.data.value);
+ expect(printData).toStrictEqual({
+ topic: "completed-deposit",
+ txid: new Uint8Array(32).fill(0),
+ voutIndex: 0n,
+ amount: 1000n,
+ });
+ const receipt1 = txOk(
+ token.transfer({
+ amount: 999n,
+ sender: alice,
+ recipient: bob,
+ memo: new Uint8Array(1).fill(0),
+ }),
+ alice
+ );
+ expect(receipt1.value).toEqual(true);
+ const receipt2 = rov(
+ token.getBalance({
+ who: bob,
+ }),
+ bob
+ );
+ expect(receipt2.value).toEqual(999n);
+ });
+
+ test("Fail a non-protocol principal calling protocol-mint", () => {
+ const receipt = txErr(
+ token.protocolMint({
+ amount: 1000n,
+ recipient: bob,
+ }),
+ bob
+ );
+ expect(receipt.value).toEqual(errors.token.ERR_NOT_AUTH);
+ });
+
+ test("Fail transferring sbtc when not owner", () => {
+ const receipt1 = txErr(
+ token.transfer({
+ amount: 999n,
+ sender: alice,
+ recipient: bob,
+ memo: new Uint8Array(1).fill(0),
+ }),
+ bob
+ );
+ expect(receipt1.value).toEqual(errors.token.ERR_NOT_OWNER);
+ });
+ });
+});