diff --git a/NFT_USE_CASE.md b/NFT_USE_CASE.md deleted file mode 100644 index 45dc1db77a..0000000000 --- a/NFT_USE_CASE.md +++ /dev/null @@ -1,377 +0,0 @@ -# NFT Use Case Demo - -## Introduction - -In this demo, you will learn how to: - -1. Publish an NFT contract on the Stacks (L1) chain & subnet (L2) respectively. -2. Register NFT asset with the subnet interface contract. -3. Mint an NFT on the L1 chain. -4. Deposit that NFT to the subnet. -5. Transfer the NFT in the subnet. -6. Withdraw the NFT back to the L1 chain, which has two steps. - -▶️ -[Watch distributed systems engineer Pavi demonstrate interacting with a Subnet here.](https://www.youtube.com/watch?v=GbGNlOsPDXM) - -This guide will follow the list above, step by step. It is also possible to mint -assets directly on the subnet, and withdraw them onto the L1. This bonus step is -mentioned at the end of step 5. - -## Subnet Background - -A subnet is a network that is separate from the Stacks chain. A subnet can be -thought of as a layer-2 (L2), and the Stacks chain can be thought of as a -layer-1 (L1). The subnet interfaces with the Stacks chain via a smart contract -that is specific to the subnet. Different subnet networks will use distinct -Stacks contracts as an interface. This interface contract has several functions -that allow it to act as an intermediary between the Stacks chain and some -particular subnet. These functions include but are not limited to: - -- `commit-block`: Called by subnet miners to record block hashes and withdrawal - state on the Stacks chain. -- `deposit-ft-asset` / `deposit-stx` / `deposit-nft-asset`: Called by users to - deposit assets into the subnet contract. The subnet "listens" for calls to - these functions, and performs a mint on the subnet to replicate this state. - Meanwhile, on the L1, the assets live in the contract. -- `withdraw-ft-asset` / `withdraw-stx` / `withdraw-nft-asset`: Called by miners - to withdraw assets from the subnet. In an upcoming update to the subnet repo, - this function will be called by users directly. - -In order to register new allowed assets, a valid miner may call -`setup-allowed-contracts`, `register-ft-contract`, or `register-nft-contract`. -The transaction sender must be part of the miners list defined in the subnet -contract. - -## Setup - -Make sure you have `clarinet` installed locally, and that it is at version -0.33.0 or above. If you do not have clarinet, you can find installation -instructions [here](https://github.com/hirosystems/clarinet). - -Let's create a new Clarinet project. This will create a new directory with the -Clarinet project initialized. - -``` -clarinet new nft-use-case -``` - -Let us copy contract files and scripts over from the `stacks-subnets` repository -into the `nft-use-case` directory. If you don't already have the stacks-subnets -repository, you can [clone it](https://github.com/hirosystems/stacks-subnets). -Here's the command to clone the stacks-subnets repository: - -``` -git clone https://github.com/hirosystems/stacks-subnets.git -``` - -Set the environment variable `SUBNET_PATH` to the location of the stacks-subnets -repository on your computer. - -``` -export SUBNET_PATH= -``` - -Now, we can copy files from the stacks-subnets repository. These files are -contracts which will define the layer-1 and layer-2 Clarity traits for NFTs and -fungible tokens, implement an NFT in layer-1 and layer-2, and some NodeJS -scripts for helping to deploy the contracts. - -``` -mkdir nft-use-case/contracts-l2 -mkdir nft-use-case/scripts -cp $SUBNET_PATH/core-contracts/contracts/helper/simple-nft.clar nft-use-case/contracts/ -cp $SUBNET_PATH/core-contracts/contracts/helper/simple-nft-l2.clar nft-use-case/contracts-l2/ -cp $SUBNET_PATH/contrib/scripts/nft-use-case/* nft-use-case/scripts/ -cd nft-use-case/scripts -``` - -To use the scripts in this demo, we need to install some NodeJS libraries. -Before running the following instructions, make sure you have -[node](https://nodejs.org/en/) installed. - -``` -npm install -cd .. # back to nft-use-case/ -``` - -The `Devnet.toml` file in the `nft-use-case` directory is responsible for -configuring the `clarinet integrate` local network. Make the following change in -`settings/Devnet.toml` to enable the subnet: - -``` -[devnet] -... -enable_subnet_node = true -``` - -Our NFT contract relies on the published SIP-009 contract, so let's add it as a -requirement: - -``` -clarinet requirements add SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait -``` - -Let's spin up a subnet node. Before you call this, make sure that you have a -working installation of Docker running locally. - -``` -clarinet integrate -``` - -Before we publish any transactions, you will need to set up some environment -variables. These environment variables contain the address and private key of -the subnet miner, two user addresses and private keys, and the RPC URL which we -can query for subnet state. Open a separate terminal window, navigate to the -directory `nft-use-case/scripts`, and enter the following. - -``` -export AUTH_SUBNET_MINER_ADDR=ST3NBRSFKX28FQ2ZJ1MAKX58HKHSDGNV5N7R21XCP -export AUTH_SUBNET_MINER_KEY=6a1a754ba863d7bab14adbbc3f8ebb090af9e871ace621d3e5ab634e1422885e01 - -export USER_ADDR=ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND -export USER_KEY=f9d7206a47f14d2870c163ebab4bf3e70d18f5d14ce1031f3902fbbc894fe4c701 - -export ALT_USER_ADDR=ST2REHHS5J3CERCRBEPMGH7921Q6PYKAADT7JP2VB -export ALT_USER_KEY=3eccc5dac8056590432db6a35d52b9896876a3d5cbdea53b72400bc9c2099fe801 -export SUBNET_URL="http://localhost:30443" -``` - -## Step 1: Publish the NFT contract to the Stacks L1 and the Subnet - -Once the Stacks node and the subnet node boots up (use the indicators in the top -right panel to determine this), we can start to interact with the chains. To -begin with, we want to publish NFT contracts onto both the L1 and L2. When the -user deposits their L1 NFT onto the subnet, their asset gets minted by the L2 -NFT contract. The publish script takes in four arguments: the name of the -contract to be published, the filename for the contract source code, the layer -on which to broadcast the transaction (1 or 2), and the nonce of the -transaction. First, publish the layer 1 contracts. You can enter this command -(and the following transaction commands) in the same terminal window as you -entered the environment variables. Make sure you are in the `scripts` directory. -These transactions are called by the principal `USER_ADDR`. - -``` -node ./publish_tx.js simple-nft-l1 ../contracts/simple-nft.clar 1 0 -``` - -Verify that the contracts were published by using the Clarinet console. For the -layer 1 contracts, you should see the following in the "transactions" region in -a recent block. - -🟩 deployed: ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND.trait-standards (ok true) - -🟩 deployed: ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND.simple-nft-l1 (ok true) - -Then, publish the layer 2 contracts. Note, it might take a minute for the subnet -node to start accepting transactions, so these commands could fail if you send -them too early (but you can always re-try when the node is ready). These -transactions are called by the principal `USER_ADDR`. - -``` -node ./publish_tx.js simple-nft-l2 ../contracts-l2/simple-nft-l2.clar 2 0 -``` - -To verify that the layer 2 transactions were processed, grep the subnet log for -the transaction IDs of _each_ subnet transaction. The transaction ID is logged -to the console after the call to `publish_tx` - make sure this is the ID you -grep for. - -``` -docker logs subnet-node.nft-use-case.devnet 2>&1 | grep "17901e5ad0587d414d5bb7b1c24c3d17bb1533f5025d154719ba1a2a0f570246" -``` - -Look for a log line similar to the following in the results: - -``` -Jul 19 12:34:41.683519 INFO Tx successfully processed. (ThreadId(9), src/chainstate/stacks/miner.rs:235), event_name: transaction_result, tx_id: 17901e5ad0587d414d5bb7b1c24c3d17bb1533f5025d154719ba1a2a0f570246, event_type: success, payload: SmartContract -``` - -To ensure the contracts were successfully parsed and published, we will grep for -the name of the contract and ensure there are no error lines returned (not -atypical for no lines to be returned at this step). - -``` -docker logs subnet-node.nft-use-case.devnet 2>&1 | grep "simple-nft-l2" -``` - -## Step 2: Register the new NFT asset in the interface subnet contract - -Create the transaction to register the new NFT asset we just published. This -must be called by a miner of the subnet contract. Specifically, this transaction -will be sent by `AUTH_SUBNET_MINER_ADDR`. - -``` -node ./register_nft.js -``` - -Look for the following transaction confirmation in the Clarinet console in an -upcoming block on the layer 1. - -🟩 invoked: -ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.subnet::register-new-nft-contract(ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG.simple-nft-l1, -"subnet-deposit-nft-token") (ok true) - -## Step 3: Mint an NFT on the L1 Chain - -Let's create a transaction to mint an NFT on the L1 chain. Once this transaction -is processed, the principal `USER_ADDR` will own an NFT. - -``` -node ./mint_nft.js 1 -``` - -Verify that the transaction is acknowledged within the next few blocks in the -Stacks explorer. - -🟩 invoked: -ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND.simple-nft-l1::gift-nft(ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND, -u5) (ok true) - -## Step 4: Deposit the NFT onto the Subnet - -Now, we can call the deposit NFT function in the subnet interface contract. This -function is called by the principal `USER_ADDR`. - -``` -node ./deposit_nft.js 2 -``` - -Verify that the transaction is acknowledged in the next few blocks of the L1 -chain. After the transaction is confirmed in an anchored block on the L1 (this -means it is included in an explicitly numbered block in the Clarinet console), -you also may want to verify that the asset was successfully deposited on the -subnet by grepping for the deposit transaction ID. - -``` -docker logs subnet-node.nft-use-case.devnet 2>&1 | grep "8d042c14323cfd9d31e121cc48c2c641a8db01dce19a0f6dd531eb33689dff44" -``` - -Look for a line like: - -``` -Jul 19 12:51:02.396923 INFO ACCEPTED burnchain operation (ThreadId(8), src/chainstate/burn/db/sortdb.rs:3042), op: deposit_nft, l1_stacks_block_id: 8b5c4eb05afae6daaafdbd59aecaade6da1a8eab5eb1041062c6381cd7104b75, txid: 67cfd6220ed01c3aca3912c8f1ff55d374e5b3acadb3b995836ae913108e0514, l1_contract_id: ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG.simple-nft-l1, subnet_contract_id: ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG.simple-nft-l2, subnet_function_name: subnet-deposit-nft-token, id: 5, sender: ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG -``` - -## Step 5: Transfer the NFT within the Subnet - -On the subnet, the NFT should belong to the principal that sent the deposit -transaction, `USER_ADDR`. This principal can now transfer the NFT within the -subnet. The principal `USER_ADDR` will now make a transaction to transfer the -NFT to `ALT_USER_ADDR`. - -``` -node ./transfer_nft.js 1 -``` - -Grep for the transaction ID of the transfer transaction. - -``` -docker logs subnet-node.nft-use-case.devnet 2>&1 | grep "6acc2c756ddaed2c4cfb7351dd5930aa93ba923504be85e47db056c99a7e81aa" -``` - -Look for something like the following line: - -``` -Jul 19 13:04:43.177993 INFO Tx successfully processed. (ThreadId(9), src/chainstate/stacks/miner.rs:235), event_name: transaction_result, tx_id: 74949992488b2519e2d8408169f242c86a6cdacd927638bd4604b3b8d48ea187, event_type: success, payload: ContractCall -``` - -For a bonus step, you can try minting an NFT on the subnet. This would require -calling the `gift-nft` function in the contract `simple-nft-l2`. You can tweak -the `transfer_nft.js` file to make this call. - -## Step 6: Withdraw the NFT back to the L1 Chain - -### Background on withdrawals - -Withdrawals from the subnet are a 2-step process. - -The owner of an asset must call `withdraw-ft?` / `withdraw-stx?` / -`withdraw-nft?` in a Clarity contract on the subnet, which destroys those assets -on the subnet, and adds that particular withdrawal to a withdrawal data -structure for that block. The withdrawal data structure serves as a -cryptographic record of the withdrawals in a particular block, and has an -overall associated hash. This hash is committed to the L1 interface contract via -the `commit-block` function. - -The second step involves calling the appropriate withdraw function in the subnet -interface contract on the L1 chain. You must also pass in the "proof" that -corresponds to your withdrawal. This proof includes the hash of the withdrawal -data structure that this withdrawal was included in, the hash of the withdrawal -itself, and a list of hashes to be used to prove that the particular withdrawal -is valid. Currently, this function must be called by a subnet miner, but in an -upcoming subnet release, the asset owner must call this function. - -### Step 6a: Withdraw the NFT on the subnet - -Perform the withdrawal on the layer 2 by calling `withdraw-nft-asset` in the -`simple-nft-l2` contract. This will be called by the principal `ALT_USER_ADDR`. - -``` -node ./withdraw_nft_l2.js 0 -``` - -Grep the subnet node to ensure success: - -``` -docker logs subnet-node.nft-use-case.devnet 2>&1 | grep "5b5407ab074b4d78539133fe72020b18d44535a586574d0bd1f668e05dc89c2f" -Jul 19 13:07:33.804109 INFO Tx successfully processed. (ThreadId(9), src/chainstate/stacks/miner.rs:235), event_name: transaction_result, tx_id: 3ff9b9b0f33dbd6087f302fa9a7a113466cf7700ba7785a741b391f5ec7c5ba4, event_type: success, payload: ContractCall - -docker logs subnet-node.nft-use-case.devnet 2>&1 | grep "withdraw-nft-asset" -Jul 19 13:22:34.800652 INFO Contract-call successfully processed (ThreadId(8), src/chainstate/stacks/db/transactions.rs:731), contract_name: ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG.simple-nft-l2, function_name: withdraw-nft-asset, function_args: [u5, ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC], return_value: (ok true), cost: ExecutionCost { write_length: 2, write_count: 2, read_length: 1647, read_count: 5, runtime: 2002000 } -``` - -In order to successfully complete the withdrawal on the L1, it is necessary to -know the height at which the withdrawal occurred. You can find the height of the -withdrawal using grep: - -``` -docker logs subnet-node.nft-use-case.devnet 2>&1 | grep "Parsed L2 withdrawal event" -Jul 19 13:22:34.801290 INFO Parsed L2 withdrawal event (ThreadId(8), src/clarity_vm/withdrawal.rs:56), type: nft, block_height: 47, sender: ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC, withdrawal_id: 0, asset_id: ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG.simple-nft-l2::nft-token -``` - -Get the withdrawal height by looking at the `block_height` in the returned line. -There may be multiple lines returned by the grep. Try the higher heights first, -and work backward. - -### Step 6b: Complete the withdrawal on the Stacks chain - -Use the withdrawal height we just obtained from the grep and substitute that for -`WITHDRAWAL_BLOCK_HEIGHT`. You might need to wait a little bit for the subnet -block to become official (even if the grep already returned a result) for the -transaction to succeed. If the subnet has not advanced sufficiently, you may get -the error `Supplied block height not found`. For now, this script assumes that -the requested withdrawal was the only one in the subnet block it was a part of -(thus, you may run into issues using this script if you are attempting to -withdraw multiple assets in a short span of time). - -``` -node ./withdraw_nft_l1.js {WITHDRAWAL_BLOCK_HEIGHT} 0 -``` - -Check for the success of this transaction in the Clarinet console: - -🟩 invoked: -ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.subnet::withdraw-nft-asset(u5, -ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05... - -You can also navigate to the Stacks Explorer (the URL of this will be listed in -the Clarinet console), and check that the expected principal now owns the NFT -(`ALT_USER_ADDR`). You can check this by clicking on the transaction -corresponding to `withdraw-nft-asset`. - -That is the conclusion of this demo! If you have any issues with this demo, -reach out on the Stacks Discord or leave an issue in the stacks-subnets -repository. - -Verify that the correct address now owns the NFT by calling: - -``` -node ./verify.js -``` - -The result is printed to the terminal, and should show: - -``` -(some ST2REHHS5J3CERCRBEPMGH7921Q6PYKAADT7JP2VB) -``` diff --git a/docs/developer-resources.md b/docs/developer-resources.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/feature-guides/trust-models.md b/docs/feature-guides/trust-models.md new file mode 100644 index 0000000000..dcad3615d7 --- /dev/null +++ b/docs/feature-guides/trust-models.md @@ -0,0 +1,17 @@ +--- +title: Trust Models +--- + +## Overview + +The current subnet implementation uses a federated system of miners. This +federation is fully-trusted, but future work on the subnet feature will explore alternative +trust models. + +In this fully-trusted federation model: + +- The federation is responsible for issuing subnet blocks. +- Users can validate these blocks, but the subnet's federation still controls + the blocks. +- Each block must be signed by a majority of the federation +- Federation signatures are validated on the L1 chain diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000000..416ddce0ef --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,857 @@ +--- +# The default id is the same as the one being defined below. so not needed +title: Getting Started +--- + +# Getting Started + +Developers can test their applications on a subnet either locally, or on Hiro's +hosted testnet subnet. This page describes two different walkthroughs that illustrate how to use a subnet. + +- Run a local subnet +- Use Hiro's subnet on testnet + +> **_NOTE:_** +> +> A subnet was previously referred to as a hyperchain. While the process of updating +> the content is ongoing, there may still be some references to a hyperchain +> instead of a subnet. + +## Run a local subnet + +Clarinet provides a tool to set up a complete local development environment, +referred to as "devnet", which uses Docker to spin up a Bitcoin node, a Stacks +node, a Stacks API node, a Stacks Explorer, and now, a subnet node and subnet +API node. This allows developers to test locally on a system that matches the +production environment. + +In this section, we will explain how to launch and interact with this devnet +subnet environment using a simple NFT example project. + +Make sure you have `clarinet` installed, and the clarinet version is at 1.5.0 or +above. If you do not already have clarinet installed, please refer to the +clarinet installation instructions +[here](https://docs.hiro.so/smart-contracts/clarinet#installing-clarinet) for +installation procedures. + +### Create a new project with Clarinet + +To create a new project, run: + +```sh +clarinet new subnet-nft-example +cd subnet-nft-example +``` + +This command creates a new directory with a clarinet project already +initialized, and then switches into that directory. + +### Create the contracts + +Clarinet does not yet support deploying a contract to a subnet, so we will not +use it to manage our subnet contracts in this guide. Instead, we will manually +deploy our subnet contracts for now. + +#### Creating the Stacks (L1) contract + +Our L1 NFT contract is going to implement the +[SIP-009 NFT trait](https://github.com/stacksgov/sips/blob/main/sips/sip-009/sip-009-nft-standard.md#trait). + +We will add this to our project as a requirement so that Clarinet will deploy it +for us. + +```sh +clarinet requirements add SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait +``` + +Now, we will use Clarinet to create our L1 contract: + +```sh +clarinet contract new simple-nft-l1 +``` + +This creates the file, _./contracts/simple-nft-l1.clar_, which will include the following clarity code: + +```clarity +(define-constant CONTRACT_OWNER tx-sender) +(define-constant CONTRACT_ADDRESS (as-contract tx-sender)) + +(define-constant ERR_NOT_AUTHORIZED (err u1001)) + +(impl-trait 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait) +(impl-trait 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.subnet-traits.mint-from-subnet-trait) + +(define-data-var lastId uint u0) +(define-map CFG_BASE_URI bool (string-ascii 256)) + +(define-non-fungible-token nft-token uint) + +(define-read-only (get-last-token-id) + (ok (var-get lastId)) +) + +(define-read-only (get-owner (id uint)) + (ok (nft-get-owner? nft-token id)) +) + +(define-read-only (get-token-uri (id uint)) + (ok (map-get? CFG_BASE_URI true)) +) + +(define-public (transfer (id uint) (sender principal) (recipient principal)) + (begin + (asserts! (is-eq tx-sender sender) ERR_NOT_AUTHORIZED) + (nft-transfer? nft-token id sender recipient) + ) +) + +;; test functions +(define-public (test-mint (recipient principal)) + (let + ((newId (+ (var-get lastId) u1))) + (var-set lastId newId) + (nft-mint? nft-token newId recipient) + ) +) + +(define-public (mint-from-subnet (id uint) (sender principal) (recipient principal)) + (begin + ;; Check that the tx-sender is the provided sender + (asserts! (is-eq tx-sender sender) ERR_NOT_AUTHORIZED) + + (nft-mint? nft-token id recipient) + ) +) + +(define-public (gift-nft (recipient principal) (id uint)) + (begin + (nft-mint? nft-token id recipient) + ) +) +``` + +Note that this contract implements the `mint-from-subnet-trait`, in addition to +the SIP-009 `nft-trait`. When `mint-from-subnet-trait` is implemented, it allows +an NFT to be minted on the subnet, then later withdrawn to the L1. + +#### Creating the subnet (L2) contract + +Next, we will create the subnet contract at _./contracts/simple-nft-l2.clar_. As +mentioned earlier, Clarinet does not support deploying subnet contracts yet, so +we will manually create this file, and add the following contents: + +```clarity +(define-constant CONTRACT_OWNER tx-sender) +(define-constant CONTRACT_ADDRESS (as-contract tx-sender)) + +(define-constant ERR_NOT_AUTHORIZED (err u1001)) + +(impl-trait 'ST000000000000000000002AMW42H.subnet.nft-trait) + +(define-data-var lastId uint u0) + +(define-non-fungible-token nft-token uint) + + +;; NFT trait functions +(define-read-only (get-last-token-id) + (ok (var-get lastId)) +) + +(define-read-only (get-owner (id uint)) + (ok (nft-get-owner? nft-token id)) +) + +(define-read-only (get-token-uri (id uint)) + (ok (some "unimplemented")) +) + +(define-public (transfer (id uint) (sender principal) (recipient principal)) + (begin + (asserts! (is-eq tx-sender sender) ERR_NOT_AUTHORIZED) + (nft-transfer? nft-token id sender recipient) + ) +) + +;; mint functions +(define-public (mint-next (recipient principal)) + (let + ((newId (+ (var-get lastId) u1))) + (var-set lastId newId) + (nft-mint? nft-token newId recipient) + ) +) + +(define-public (gift-nft (recipient principal) (id uint)) + (begin + (nft-mint? nft-token id recipient) + ) +) + +(define-read-only (get-token-owner (id uint)) + (nft-get-owner? nft-token id) +) + +(impl-trait 'ST000000000000000000002AMW42H.subnet.subnet-asset) + +;; Called for deposit from the burnchain to the subnet +(define-public (deposit-from-burnchain (id uint) (recipient principal)) + (begin + (asserts! (is-eq tx-sender 'ST000000000000000000002AMW42H) ERR_NOT_AUTHORIZED) + (nft-mint? nft-token id recipient) + ) +) + +;; Called for withdrawal from the subnet to the burnchain +(define-public (burn-for-withdrawal (id uint) (owner principal)) + (begin + (asserts! (is-eq tx-sender owner) ERR_NOT_AUTHORIZED) + (nft-burn? nft-token id owner) + ) +) +``` + +Note that this contract implements the `nft-trait` and the `subnet-asset` trait. +The `nft-trait` is the same as the SIP-009 trait on the Stacks network. +`subnet-asset` defines the functions required for deposit and withdrawal. +`deposit-from-burnchain` is invoked by the subnet node's consensus logic +whenever a deposit is made in layer-1. `burn-for-withdrawal` is invoked by the +`nft-withdraw?` or `ft-withdraw?` functions of the subnet contract, that a user +calls when they wish to withdraw their asset from the subnet back to the +layer-1. + +### Start the devnet + +The settings for the devnet are found in _./settings/Devnet.toml_. In order to +launch a subnet in the devnet, we need to tell Clarinet to enable a subnet node +and a corresponding API node. Because the subnet contract requires Stacks 2.1 +support, we must also enable "next" features. + +Add, or uncomment, the following lines under `[devnet]`: + +```toml +enable_subnet_node = true +disable_subnet_api = false +enable_next_features = true +``` + +Run the following command to start the devnet environment: + +```sh +clarinet integrate +``` + +This will launch docker containers for a bitcoin node, a Stacks node, the Stacks +API service, a subnet node, the subnet API service, and an explorer service. +While running, `clarinet integrate` opens a terminal UI that shows various +data points about the state of the network. + +All of the nodes and services are running and ready when we see: + +![Clarinet integrate services](images/subnet-devnet.png) + +Once this state is reached, we should see successful calls to `commit-block` in +the transactions console. This is the subnet miner committing blocks to the L1. +Leave this running and perform the next steps in another terminal. + +### Setup Node.js scripts + +To submit transactions to Hiro's Stacks node and subnet node, we will use +[Stacks.js](https://stacks.js.org) and some simple scripts. We will start by +creating a new directory, _./scripts/_ for these scripts. + +```sh +mkdir scripts +cd scripts +``` + +Then we will initialize a Node.js project and install the stacks.js dependencies: + +```sh +npm init -y +npm install @stacks/network @stacks/transactions +``` + +In the generated `package.json` file, add the following into the `json` to enable +modules: + +```json + "type": "module", +``` + +To simplify our scripts, we will define some environment variables that will be +used to hold the signing keys for various subnet transactions. + +```sh +export DEPLOYER_ADDR=ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM +export DEPLOYER_KEY=753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a601 + +export USER_ADDR=ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND +export USER_KEY=f9d7206a47f14d2870c163ebab4bf3e70d18f5d14ce1031f3902fbbc894fe4c701 + +export ALT_USER_ADDR=ST2REHHS5J3CERCRBEPMGH7921Q6PYKAADT7JP2VB +export ALT_USER_KEY=3eccc5dac8056590432db6a35d52b9896876a3d5cbdea53b72400bc9c2099fe801 +export SUBNET_URL="http://localhost:30443" +``` + +#### Publish contract script + +We will start with a script to publish a contract. To make it reusable, we will +allow this script to handle some command line arguments: + +1. Contract name +2. Path to contract +3. Network layer (1 = Stacks, 2 = Subnet) +4. The deployer's current account nonce + +_publish.js_: + +```js +import { + AnchorMode, + makeContractDeploy, + broadcastTransaction, +} from "@stacks/transactions"; +import { StacksTestnet, HIRO_MOCKNET_DEFAULT } from "@stacks/network"; +import { readFileSync } from "fs"; + +async function main() { + const contractName = process.argv[2]; + const contractFilename = process.argv[3]; + const networkLayer = parseInt(process.argv[4]); + const nonce = parseInt(process.argv[5]); + const senderKey = process.env.USER_KEY; + const networkUrl = + networkLayer == 2 ? process.env.SUBNET_URL : HIRO_MOCKNET_DEFAULT; + + const codeBody = readFileSync(contractFilename, { encoding: "utf-8" }); + + const transaction = await makeContractDeploy({ + codeBody, + contractName, + senderKey, + network: new StacksTestnet({ url: networkUrl }), + anchorMode: AnchorMode.Any, + fee: 10000, + nonce, + }); + + const txid = await broadcastTransaction( + transaction, + new StacksTestnet({ url: networkUrl }) + ); + + console.log(txid); +} + +main(); +``` + +#### Register NFT script + +We also need to register our NFT with our subnet, allowing it to be deposited into the subnet. To do this, we'll write another script, but because we only need to do this once, we will hardcode our details into the script. + +This script calls `register-new-nft-contract` on the L1 subnet contract, passing +the L1 and L2 NFT contracts we will publish. + +_register.js_: + +```js +import { + makeContractCall, + AnchorMode, + contractPrincipalCV, + broadcastTransaction, + getNonce, +} from "@stacks/transactions"; +import { StacksTestnet, HIRO_MOCKNET_DEFAULT } from "@stacks/network"; + +async function main() { + const network = new StacksTestnet({ url: HIRO_MOCKNET_DEFAULT }); + const senderKey = process.env.DEPLOYER_KEY; + const deployerAddr = process.env.DEPLOYER_ADDR; + const userAddr = process.env.USER_ADDR; + const nonce = await getNonce(deployerAddr, network); + + const txOptions = { + contractAddress: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + contractName: "subnet", + functionName: "register-new-nft-contract", + functionArgs: [ + contractPrincipalCV(deployerAddr, "simple-nft-l1"), + contractPrincipalCV(userAddr, "simple-nft-l2"), + ], + senderKey, + validateWithAbi: false, + network, + anchorMode: AnchorMode.Any, + fee: 10000, + nonce, + }; + + const transaction = await makeContractCall(txOptions); + + const txid = await broadcastTransaction(transaction, network); + + console.log(txid); +} + +main(); +``` + +#### Mint NFT script + +In order to move NFTs to and from the subnet, we will need to have some +NFTs on our devnet. To do this, we need to mint, so we also write a script +for submitting NFT mint transactions to the layer-1 network. This script +takes just one argument: the user's current account nonce. + +_mint.js_: + +```js +import { + makeContractCall, + AnchorMode, + standardPrincipalCV, + uintCV, + broadcastTransaction, +} from "@stacks/transactions"; +import { StacksTestnet, HIRO_MOCKNET_DEFAULT } from "@stacks/network"; + +async function main() { + const network = new StacksTestnet({ url: HIRO_MOCKNET_DEFAULT }); + const senderKey = process.env.USER_KEY; + const deployerAddr = process.env.DEPLOYER_ADDR; + const addr = process.env.USER_ADDR; + const nonce = parseInt(process.argv[2]); + + const txOptions = { + contractAddress: deployerAddr, + contractName: "simple-nft-l1", + functionName: "gift-nft", + functionArgs: [standardPrincipalCV(addr), uintCV(5)], + senderKey, + validateWithAbi: false, + network, + anchorMode: AnchorMode.Any, + fee: 10000, + nonce, + }; + + const transaction = await makeContractCall(txOptions); + + const txid = await broadcastTransaction(transaction, network); + + console.log(txid); +} + +main(); +``` + +#### Deposit NFT script + +We also want to be able to deposit an asset into the subnet. To do this, we will +write another script to call the `deposit-nft-asset` function on the layer-1 +subnet contract. Like the NFT minting script, this script takes just one +argument: the user's current account nonce. + +_deposit.js_ + +```js +import { + makeContractCall, + AnchorMode, + standardPrincipalCV, + uintCV, + contractPrincipalCV, + PostConditionMode, + broadcastTransaction, +} from "@stacks/transactions"; +import { StacksTestnet, HIRO_MOCKNET_DEFAULT } from "@stacks/network"; + +async function main() { + const network = new StacksTestnet({ url: HIRO_MOCKNET_DEFAULT }); + const senderKey = process.env.USER_KEY; + const addr = process.env.USER_ADDR; + const deployerAddr = process.env.DEPLOYER_ADDR; + const nonce = parseInt(process.argv[2]); + + const txOptions = { + contractAddress: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + contractName: "subnet", + functionName: "deposit-nft-asset", + functionArgs: [ + contractPrincipalCV(deployerAddr, "simple-nft-l1"), // contract ID of nft contract on L1 + uintCV(5), // ID + standardPrincipalCV(addr), // sender + ], + senderKey, + validateWithAbi: false, + network, + anchorMode: AnchorMode.Any, + fee: 10000, + postConditionMode: PostConditionMode.Allow, + nonce, + }; + + const transaction = await makeContractCall(txOptions); + + const txid = await broadcastTransaction(transaction, network); + + console.log(txid); +} + +main(); +``` + +#### Transfer NFT script + +To demonstrate some subnet transactions, we will want to transfer an NFT from one +user to another. We will write another script to invoke the NFT's `transfer` +function in the subnet. Again, this script takes just one argument: the user's +current account nonce. + +_transfer.js_ + +```js +import { + makeContractCall, + AnchorMode, + standardPrincipalCV, + uintCV, + PostConditionMode, + broadcastTransaction, +} from "@stacks/transactions"; +import { StacksTestnet } from "@stacks/network"; + +async function main() { + const network = new StacksTestnet({ url: process.env.SUBNET_URL }); + const senderKey = process.env.USER_KEY; + const addr = process.env.USER_ADDR; + const alt_addr = process.env.ALT_USER_ADDR; + const nonce = parseInt(process.argv[2]); + + const txOptions = { + contractAddress: addr, + contractName: "simple-nft-l2", + functionName: "transfer", + functionArgs: [ + uintCV(5), // ID + standardPrincipalCV(addr), // sender + standardPrincipalCV(alt_addr), // recipient + ], + senderKey, + validateWithAbi: false, + network, + anchorMode: AnchorMode.Any, + fee: 10000, + nonce, + postConditionMode: PostConditionMode.Allow, + }; + + const transaction = await makeContractCall(txOptions); + + const txid = await broadcastTransaction(transaction, network); + + console.log(txid); +} + +main(); +``` + +#### L2 withdraw script + +In order to withdraw an asset from a subnet, users must first submit a withdraw +transaction on that subnet. To support this, we will write a script that invokes +the `nft-withdraw?` method on the layer-2 subnet contract. This script takes +just a single argument: the user's current account nonce. + +_withdraw-l2.js_ + +```js +import { + makeContractCall, + AnchorMode, + standardPrincipalCV, + contractPrincipalCV, + uintCV, + broadcastTransaction, + PostConditionMode, +} from "@stacks/transactions"; +import { StacksTestnet } from "@stacks/network"; + +async function main() { + const network = new StacksTestnet({ url: process.env.SUBNET_URL }); + const senderKey = process.env.ALT_USER_KEY; + const contractAddr = process.env.USER_ADDR; + const addr = process.env.ALT_USER_ADDR; + const nonce = parseInt(process.argv[2]); + + const txOptions = { + contractAddress: "ST000000000000000000002AMW42H", + contractName: "subnet", + functionName: "nft-withdraw?", + functionArgs: [ + contractPrincipalCV(contractAddr, "simple-nft-l2"), + uintCV(5), // ID + standardPrincipalCV(addr), // recipient + ], + senderKey, + validateWithAbi: false, + network, + anchorMode: AnchorMode.Any, + fee: 10000, + nonce, + postConditionMode: PostConditionMode.Allow, + }; + + const transaction = await makeContractCall(txOptions); + + const txid = await broadcastTransaction(transaction, network); + + console.log(txid); +} + +main(); +``` + +#### L1 withdraw script + +The second step of a withdrawal is to call the `withdraw-nft-asset` method on +the layer-1 subnet contract. This method requires information from the subnet to +verify that the withdrawal is valid. We will write a script that queries our +subnet node's RPC interface for this information and then issues the layer-1 +withdrawal transaction. + +This scripts has two input arguments: the (subnet) block height of the layer-2 +withdrawal transaction, and the user's current account nonce. + +_withdraw-l1.js_ + +```js +import { + makeContractCall, + deserializeCV, + AnchorMode, + standardPrincipalCV, + uintCV, + someCV, + PostConditionMode, + contractPrincipalCV, + broadcastTransaction, +} from "@stacks/transactions"; +import { StacksTestnet, HIRO_MOCKNET_DEFAULT } from "@stacks/network"; + +async function main() { + const network = new StacksTestnet({ url: HIRO_MOCKNET_DEFAULT }); + const subnetUrl = process.env.SUBNET_URL; + const senderKey = process.env.ALT_USER_KEY; + const addr = process.env.ALT_USER_ADDR; + const l1ContractAddr = process.env.DEPLOYER_ADDR; + const l2ContractAddr = process.env.USER_ADDR; + const withdrawalBlockHeight = process.argv[2]; + const nonce = parseInt(process.argv[3]); + const withdrawalId = 0; + + let json_merkle_entry = await fetch( + `${subnetUrl}/v2/withdrawal/nft/${withdrawalBlockHeight}/${addr}/${withdrawalId}/${l2ContractAddr}/simple-nft-l2/5` + ).then((x) => x.json()); + let cv_merkle_entry = { + withdrawal_leaf_hash: deserializeCV(json_merkle_entry.withdrawal_leaf_hash), + withdrawal_root: deserializeCV(json_merkle_entry.withdrawal_root), + sibling_hashes: deserializeCV(json_merkle_entry.sibling_hashes), + }; + + const txOptions = { + senderKey, + network, + anchorMode: AnchorMode.Any, + contractAddress: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + contractName: "subnet", + functionName: "withdraw-nft-asset", + functionArgs: [ + contractPrincipalCV(l1ContractAddr, "simple-nft-l1"), // nft-contract + uintCV(5), // ID + standardPrincipalCV(addr), // recipient + uintCV(withdrawalId), // withdrawal ID + uintCV(withdrawalBlockHeight), // withdrawal block height + someCV(contractPrincipalCV(l1ContractAddr, "simple-nft-l1")), // nft-mint-contract + cv_merkle_entry.withdrawal_root, // withdrawal root + cv_merkle_entry.withdrawal_leaf_hash, // withdrawal leaf hash + cv_merkle_entry.sibling_hashes, + ], // sibling hashes + fee: 10000, + postConditionMode: PostConditionMode.Allow, + nonce, + }; + + const transaction = await makeContractCall(txOptions); + + const txid = await broadcastTransaction(transaction, network); + + console.log(txid); +} + +main(); +``` + +#### Verify script + +Lastly, we need a simple way to query for the current owner of an NFT, so we +will write a script that invokes the read-only `get-owner` function via either +the subnet or stacks node's RPC interface. This script takes just one argument +indicating whether it should query the subnet (`2`) or the stacks node (`1`). + +_verify.js_ + +```js +import { + uintCV, + callReadOnlyFunction, + cvToString, + cvToHex, + hexToCV, +} from "@stacks/transactions"; +import { StacksTestnet, HIRO_MOCKNET_DEFAULT } from "@stacks/network"; + +async function main() { + const networkLayer = parseInt(process.argv[2]); + const senderAddress = process.env.ALT_USER_ADDR; + const contractAddress = + networkLayer == 2 ? process.env.USER_ADDR : process.env.DEPLOYER_ADDR; + const networkUrl = + networkLayer == 2 ? process.env.SUBNET_URL : HIRO_MOCKNET_DEFAULT; + const network = new StacksTestnet({ url: networkUrl }); + const contractName = networkLayer == 2 ? "simple-nft-l2" : "simple-nft-l1"; + + const txOptions = { + contractAddress, + contractName, + functionName: "get-owner", + functionArgs: [uintCV(5)], + network, + senderAddress, + }; + + const result = await callReadOnlyFunction(txOptions); + + console.log(cvToString(result.value)); +} + +main(); +``` + +### Interacting with the subnet + +We will now use this set of scripts to demonstrate a subnet's functionality. We will: + +1. Publish our NFT contract on the subnet +2. Mint a new NFT in the stacks network +3. Deposit this NFT into the subnet +4. Transfer the NFT from one user to another in the subnet +5. Withdraw the NFT from the subnet + +First, we will publish the L2 NFT contract to the subnet: + +```sh +node ./publish.js simple-nft-l2 ../contracts/simple-nft-l2.clar 2 0 +``` + +Clarinet's interface doesn't show the transactions on the subnet, but we can see +the transaction in our local explorer instance. In a web browser, visit +http://localhost:8000. By default, it will open the explorer for the devnet L1. +To switch to the subnet, click on "Network" in the top right, then "Add a +network". In the popup, choose a name, e.g. "Devnet Subnet", then for the URL, +use "http://localhost:13999". You will know this contract deployment succeedeed +when you see the contract deploy transaction for "simple-nft-l2" in the list of +confirmed transactions. + +![contract deploy confirmed](images/confirmed.png) + +Now that the NFT contracts are deployed to both the L1 and the L2, we will +register the NFT with the subnet. + +```sh +node ./register.js +``` + +This is an L1 transaction, so you can watch for it in the Clarinet interface or +in the Devnet network on the explorer. + +Now, we need an asset to work with, so we will mint an NFT on the L1: + +```js +node ./mint.js 0 +``` + +We can see this transaction either on the Clarinet interface or in the Devnet +network on the explorer. + +Once the mint has been processed, we can deposit it into the subnet: + +```js +node ./deposit.js 1 +``` + +We can see this transaction either on the Clarinet interface or in the Devnet +network on the explorer. + +We can verify that the NFT is now owned by the subnet contract +(`ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.subnet`) on the L1 using: + +```js +node ./verify.js 1 +``` + +Similarly, we can verify that the NFT is owned by the expected address +(`ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND`) on the L2: + +```js +node ./verify.js 2 +``` + +Now that the NFT is inside the subnet, we can transfer it from one address to +another: + +```js +node ./transfer.js 1 +``` + +We can see this transaction in the "Devnet Subnet" network in our explorer. + +If we call the `verify.js` script again, we should now see that the NFT is owned +by `ST2REHHS5J3CERCRBEPMGH7921Q6PYKAADT7JP2VB`. + +Now, we will initiate a withdrawal from the subnet, by calling the +`nft-withdraw?` function on the L2 subnet contract. + +```js +node ./withdraw-l2.js 0 +``` + +We can confirm that this transaction is successful in the L2 explorer. In the +explorer, note the block height that this withdrawal transaction is included in. +Fill in this block height for `$height` in the next step. + +For the second part of the withdraw, we call `withdraw-nft-asset` on the L1 +subnet contract: + +```sh +node ./withdraw-l1.js $height 0 +``` + +This is an L1 transaction, so it can be confirmed in the L1 explorer or in the +Clarinet terminal UI. + +If everything went well, now the NFT should be owned by the correct user on the +L1 (`ST2REHHS5J3CERCRBEPMGH7921Q6PYKAADT7JP2VB`): + +```sh +node ./verify.js 1 +``` + +In the subnet, this asset should not be owned by anyone (`none`): + +```sh +node ./verify.js 2 +``` diff --git a/docs/images/confirmed.png b/docs/images/confirmed.png new file mode 100644 index 0000000000..489bdd83aa Binary files /dev/null and b/docs/images/confirmed.png differ diff --git a/docs/images/subnet-devnet.png b/docs/images/subnet-devnet.png new file mode 100644 index 0000000000..108ccd43ec Binary files /dev/null and b/docs/images/subnet-devnet.png differ diff --git a/docs/init.md b/docs/init.md deleted file mode 100644 index f3b98076c6..0000000000 --- a/docs/init.md +++ /dev/null @@ -1,95 +0,0 @@ -# Sample init scripts and service configuration for stacks-blockchain - -Sample scripts and configuration files for systemd and SysVinit -can be found in the [contrib/init](../contrib/init) folder. - - contrib/init/stacks.service: systemd service unit configuration - contrib/init/stacks.init: SysV style init script - -## Service User - -All Linux startup configurations assume the existence of a "stacks" user -and group. They must be created before attempting to use these scripts. -The MacOS configuration assumes stacks-blockchain will be set up for the current user. - -## Configuration - -For an example configuration file that describes the configuration settings, -see [mainnet-follower-conf.toml](../testnet/stacks-node/conf/mainnet-follower-conf.toml). -Available configuration options are documented here: https://docs.stacks.co/references/stacks-node-configuration - -## Paths - -### Linux - -All three configurations assume several paths that might need to be adjusted. - - Binary: /usr/local/bin/stacks-node - Configuration file: /etc/stacks-blockchain/Config.toml - Data directory: /stacks-blockchain - PID file: /run/stacks-blockchain/stacks.pid - Lock file: /var/lock/subsys/stacks (SysVinit) - -The PID directory and data directory should both be owned by the -stacks user and group. It is advised for security reasons to make the -configuration file and data directory only readable by the stacks user and -group. - -NOTE: When using the systemd .service file, the creation of the aforementioned -directories and the setting of their permissions is automatically handled by -systemd. Directories are given a permission of 710, giving the stacks group -access to files under it _if_ the files themselves give permission to the -stacks group to do so. This does not allow for the listing of files under the directory. - -```bash -$ mkdir -p /etc/stacks-blockchain/ -$ mkdir -p /stacks-blockchain -$ useradd stacks -$ chown -R stacks:stacks /stacks-blockchain/ -$ chgrp -R stacks /etc/stacks-blockchain -``` - -### macOS - - Binary: /usr/local/bin/stacks-node - Configuration file: /etc/stacks-blockchain/Config.toml - -The Config.toml file is presumed to have group ownership by the `wheel` group, with the current user having membership to that group. The data directory defined in the config is required to be writable by the current user. Logs (stdout & stderr) are saved under `/tmp/stacks-blockchain.log` - -## Installing Service Configuration - -### systemd - -Installing this .service file consists of just copying it to -/usr/lib/systemd/system directory, followed by the command -`systemctl daemon-reload` in order to update running systemd configuration. - -To test, run `systemctl start stacks` and to enable for system startup run -`systemctl enable stacks` - -NOTE: When installing for systemd in Debian/Ubuntu the .service file needs to be copied to the /lib/systemd/system directory instead. - -### SysVinit - -Copy stacks.init to /etc/init.d/stacks. Test by running `service stacks start`. - -Using this script, you can adjust the config path and log location to the stacks-node program by -setting the STACKS_BLOCKCHAIN_CONFIG and STACKS_BLOCKCHAIN_LOG environment variables in the file -/etc/sysconfig/stacks-blockchain. - -### MacOS - -Copy org.stacks.stacks-blockchain.plist into ~/Library/LaunchAgents. Load the launch agent by -running `launchctl load ~/Library/LaunchAgents/org.stacks.stacks-blockchain.plist`. - -This Launch Agent will **not** start the stacks-blockchain whenever the user logs in. - -To start the service, you'll need to manually run the start command: `launchctl start org.stacks.stacks-blockchain` - -NOTE: This approach is intended for those wanting to run stacks-blockchain as the current user. -You will need to modify org.stacks.stacks-blockchain.plist if you intend to use it as a -Launch Daemon with a dedicated stacks user. - -## Auto-respawn - -Auto respawning is currently disabled. diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 0000000000..6042a509c0 --- /dev/null +++ b/docs/overview.md @@ -0,0 +1,68 @@ +--- +title: Overview +--- + +# Overview + +A subnet is a layer-2 scaling solution in the Stacks blockchain offering low +latency and high throughput, while also enabling developers to build fast and reliable +experiences on Stacks. + +## Background + +A subnet is a network separate from the Stacks chain. A subnet can be thought of +as a layer-2 (L2), and the Stacks chain can be thought of as a layer-1 (L1). A +subnet interfaces with the Stacks chain via a smart contract specific to the +subnet. Different subnets use distinct contracts on the Stacks chain as an +interface. + +This interface contract has several functions that allow it to act as an +intermediary between the Stacks chain and some particular subnet. These +functions include, but are not limited to, the following functions: + +- `commit-block`: Called by subnet miners to record block hashes and withdrawal + states on the Stacks chain. +- `deposit-ft-asset` / `deposit-stx` / `deposit-nft-asset`: Called by users to + deposit assets into the subnet. The subnet miners "listens" for calls to these + functions, and performs a mint on the subnets to replicate this state. + Meanwhile, on the L1, the assets live in the subnet contract. +- `withdraw-ft-asset` / `withdraw-stx` / `withdraw-nft-asset`: Called by users + to withdrawal assets from the subnet. Withdrawal is a two step process, where + the user first initiates a withdrawal within the subnet, then calls these + functions on the Stacks chain to complete the withdrawal. + +In order to register new allowed assets, the subnet's administrator must call +`register-new-ft-contract`, or `register-new-nft-contract`. Only assets that +have been registered can be deposited into the subnet. + +## Features + +A subnets is designed to transact on Stacks assets, meaning users can move assets +from the Stacks chain in and out of a subnet. While a user’s assets are in a +subnet, the asset is locked in the subnet contract on the Stacks chain. + +> **_NOTE:_** +> +> The current subnet implementation relies on either a single block producer or +> a fully-trusted federation of block producers. Users of a subnet should be +> aware that they are sacrificing decentralization and security for the speed +> provided in the subnet, and therefore should only deposit assets into trusted +> subnets. + +Listed below are some of the features of a subnet: + +- Each subnet may define its throughput settings. The default implementation + should support at least 4x higher throughput for transactions and reduce + confirmation time from 10 minutes to 1 minute. +- Interacting with a subnet is similar to interacting with a different Stacks + network (for example: testnet vs. mainnet). +- The Stacks blockchain may support many different subnets. +- Each subnet may use the same or different consensus rules. +- This repository implements a consensus mechanism that uses a two-phase commit + among a federated pool of miners. +- FTs, NFTs, and STX deposits and withdrawals are supported via user-submitted + L1 transactions. +- To deposit into a subnet, users submit a layer-1 transaction to invoke the + deposit method on that subnet's smart contract. +- For withdrawals, users commit the withdrawal on the subnet and then submit a + layer-1 transaction to invoke the subnet's smart contract's withdraw method. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000000..e69de29bb2