mirror of
https://github.com/alexgo-io/stacks-subnets.git
synced 2026-01-12 22:43:44 +08:00
fix: update scripts and readme for NFT demo
This commit is contained in:
332
NFT_USE_CASE.md
332
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
|
||||
```
|
||||
|
||||
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=<YOUR_PATH_HERE>
|
||||
```
|
||||
|
||||
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.
|
||||
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.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.
|
||||
|
||||
```
|
||||
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 3
|
||||
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.
|
||||
|
||||
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`.
|
||||
|
||||
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.
|
||||
|
||||
```
|
||||
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.
|
||||
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.
|
||||
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`.
|
||||
|
||||
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
|
||||
@@ -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.
|
||||
|
||||
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).
|
||||
|
||||
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)
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
23
contrib/scripts/nft-use-case/verify.js
Normal file
23
contrib/scripts/nft-use-case/verify.js
Normal file
@@ -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();
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user