diff --git a/NFT_USE_CASE.md b/NFT_USE_CASE.md index c46d4a87f..45dc1db77 100644 --- a/NFT_USE_CASE.md +++ b/NFT_USE_CASE.md @@ -1,7 +1,9 @@ # 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. @@ -9,90 +11,123 @@ In this demo, you will learn how to: 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) +▶️ +[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. +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. +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). +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. -Let's create a new Clarinet project. This will create a new directory with the Clarinet project initialized. ``` -clarinet new nft-use-case +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). +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. + +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. +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/sip-traits.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. +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: +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 ``` -Let's spin up a subnet node. Before you call this, make sure that you have a working installation of Docker running -locally. +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. +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=ST3AM1A56AK2C1XAFJ4115ZSV26EB49BVQ10MGCS0 -export AUTH_SUBNET_MINER_KEY=7036b29cb5e235e5fd9b09ae3e8eec4404e44906814d5d01cbca968a60ed4bfb01 +export AUTH_SUBNET_MINER_ADDR=ST3NBRSFKX28FQ2ZJ1MAKX58HKHSDGNV5N7R21XCP +export AUTH_SUBNET_MINER_KEY=6a1a754ba863d7bab14adbbc3f8ebb090af9e871ace621d3e5ab634e1422885e01 export USER_ADDR=ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND export USER_KEY=f9d7206a47f14d2870c163ebab4bf3e70d18f5d14ce1031f3902fbbc894fe4c701 @@ -103,133 +138,181 @@ 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. + +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 trait-standards ../contracts/trait-standards.clar 1 0 -node ./publish_tx.js simple-nft-l1 ../contracts/simple-nft.clar 1 1 +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. +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.trait-standards (ok true) -🟩 deployed: ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND.simple-nft-l1 (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`. -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 trait-standards ../contracts-l2/trait-standards.clar 2 0 -node ./publish_tx.js simple-nft-l2 ../contracts-l2/simple-nft-l2.clar 2 1 +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. +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). +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 0 -``` -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) +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 2 -``` -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) +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. -## 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 3 +node ./mint_nft.js 1 ``` -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. + +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`. +## 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 2 +node ./transfer_nft.js 1 ``` -Grep for the transaction ID of the transfer transaction. + +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. +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. +Withdrawals from the subnet are a 2-step process. -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. +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`. -### 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 +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 @@ -238,34 +321,57 @@ 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: +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). +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} 1 +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... +🟩 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`. +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. -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/contrib/scripts/nft-use-case/deposit_stx.js b/contrib/scripts/nft-use-case/deposit_stx.js index 18ac5df53..fc747a0b0 100644 --- a/contrib/scripts/nft-use-case/deposit_stx.js +++ b/contrib/scripts/nft-use-case/deposit_stx.js @@ -15,7 +15,7 @@ async function main() { const txOptions = { contractAddress: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", - contractName: "subnet", + contractName: "subnet-alpha", functionName: "deposit-stx", functionArgs: [ uintCV(500_000_000_000), // ID diff --git a/contrib/scripts/nft-use-case/register_nft.js b/contrib/scripts/nft-use-case/register_nft.js index 05bc5cd72..3625dc26f 100644 --- a/contrib/scripts/nft-use-case/register_nft.js +++ b/contrib/scripts/nft-use-case/register_nft.js @@ -2,8 +2,8 @@ import { makeContractCall, AnchorMode, contractPrincipalCV, - stringAsciiCV, broadcastTransaction, + getNonce, } from "@stacks/transactions"; import { StacksTestnet, HIRO_MOCKNET_DEFAULT } from "@stacks/network"; @@ -11,7 +11,8 @@ async function main() { const network = new StacksTestnet({ url: HIRO_MOCKNET_DEFAULT }); const senderKey = process.env.AUTH_SUBNET_MINER_KEY; const userAddr = process.env.USER_ADDR; - const nonce = parseInt(process.argv[2]); + const nonce = + (await getNonce(process.env.AUTH_SUBNET_MINER_ADDR, network)) + 1n; const txOptions = { contractAddress: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", diff --git a/contrib/scripts/nft-use-case/verify.js b/contrib/scripts/nft-use-case/verify.js new file mode 100644 index 000000000..2ecd65920 --- /dev/null +++ b/contrib/scripts/nft-use-case/verify.js @@ -0,0 +1,23 @@ +import { uintCV, callReadOnlyFunction, cvToString } from "@stacks/transactions"; +import { StacksTestnet, HIRO_MOCKNET_DEFAULT } from "@stacks/network"; + +async function main() { + const network = new StacksTestnet({ url: HIRO_MOCKNET_DEFAULT }); + const senderAddress = process.env.ALT_USER_ADDR; + const addr = process.env.USER_ADDR; + + const txOptions = { + contractAddress: addr, + contractName: "simple-nft-l1", + functionName: "get-owner", + functionArgs: [uintCV(5)], + network, + senderAddress, + }; + + const result = await callReadOnlyFunction(txOptions); + + console.log(cvToString(result.value)); +} + +main(); diff --git a/contrib/scripts/nft-use-case/withdraw_nft_l1.js b/contrib/scripts/nft-use-case/withdraw_nft_l1.js index e76201e06..021cc7d93 100644 --- a/contrib/scripts/nft-use-case/withdraw_nft_l1.js +++ b/contrib/scripts/nft-use-case/withdraw_nft_l1.js @@ -15,7 +15,7 @@ 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.AUTH_SUBNET_MINER_KEY; + const senderKey = process.env.ALT_USER_KEY; const addr = process.env.ALT_USER_ADDR; const contractAddr = process.env.USER_ADDR; const withdrawalBlockHeight = process.argv[2]; @@ -23,8 +23,9 @@ async function main() { const withdrawalId = 0; let json_merkle_entry = await fetch( - `${subnetUrl}/v2/withdrawal/nft/${withdrawalBlockHeight}/${addr}/${withdrawalId}/${contractAddr}/simple-nft-l2/nft-token/5` + `${subnetUrl}/v2/withdrawal/nft/${withdrawalBlockHeight}/${addr}/${withdrawalId}/${contractAddr}/simple-nft-l2/5` ).then((x) => x.json()); + console.log(JSON.stringify(json_merkle_entry)); let cv_merkle_entry = { withdrawal_leaf_hash: deserializeCV(json_merkle_entry.withdrawal_leaf_hash), withdrawal_root: deserializeCV(json_merkle_entry.withdrawal_root),