Understanding Taproot Assets Protocol #2

Hiroki Gondo
Nayuta Engineering Blog
5 min readJul 10, 2023

In a previous article, the Taproot Assets Protocol (TAP) specifications were explained. This article aims to deepen understanding of the specifications by running the Taproot Assets Protocol Daemon (tapd) developed by Lightning Labs.

The steps are the same as those on the GitHub front page of the tapd but with more detailed explanations and notes.

1. Run lnd

To run tapd, we need Lightning Network Daemon (lnd). tapd accesses Bitcoin Layer 1 via lnd *. Here we use Bitcoin’s testnet.

* Lightning Network (Layer 2) functionality for lnd is not required here, but Lightning Network support for TAP would be one of the most important milestones.

My test environment is as follows.

$ sw_vers
ProductName: macOS
ProductVersion: 13.2.1
BuildVersion: 22D68
$ go version
go version go1.20.3 darwin/amd64

Install and run lnd.

$ git clone https://github.com/lightningnetwork/lnd.git
$ cd lnd
$ git log | head -1
commit 8c58730c34022cd9f3c8c7c408c0783a751f2404
$ make install tags="signrpc walletrpc chainrpc invoicesrpc"
$ lnd -V
lnd version 0.16.99-beta commit=v0.16.0-beta-332-g8c58730c3
$ lnd --bitcoin.active --bitcoin.testnet --bitcoin.node=neutrino

Create a wallet with a dedicated command line tool (lncli) from another terminal.

$ lncli -network=testnet create
Input wallet password:
Confirm password:

Do you have an existing cipher seed mnemonic or extended master root key you want to use?
Enter 'y' to use an existing cipher seed mnemonic, 'x' to use an extended master root key
or 'n' to create a new seed (Enter y/x/n): n

Your cipher seed can optionally be encrypted.
Input your passphrase if you wish to encrypt it (or press enter to proceed without a cipher seed passphrase):

Generating fresh cipher seed...



lnd successfully initialized!

The wallet must be unlocked to access lnd from outside (from tapd). Immediately after the wallet is created, the wallet is unlocked, but if lnd is rebooted, the wallet must be unlocked by a command.

$ lncli --network=testnet unlock
Input wallet password:
lnd successfully unlocked!

Wait until the state becomes SERVER_ACTIVE, as it takes a while to synchronize the chain. Now lnd is ready.

$ lncli state
{
"state": "RPC_ACTIVE"
}




$ lncli state
{
"state": "SERVER_ACTIVE"
}
$ lncli --network=testnet getinfo
{

"block_height": 2431595,

"synced_to_chain": true,

2. Run tapd

Install and run tapd.

$ git clone https://github.com/lightninglabs/taproot-assets.git
$ cd taproot-assets
$ git log | head -1
commit 1a3f86c09ac044ff3b9e27cf90a314eecb2e0625
$ make install
$ tapd --network=testnet

3. Create Asset

Create assets with a dedicated command line tool (tapcli) from another terminal.

$ tapcli assets mint --type normal --name fantasycoin --supply 100 --meta_bytes "fantastic money"
{
"batch_key": "0326764481f0945fea8cc696a52753d30409026d7dfd5652beb4904dfc44c238d1"
}
$ tapcli assets mint finalize
{
"batch_key": "0326764481f0945fea8cc696a52753d30409026d7dfd5652beb4904dfc44c238d1"
}

Error in tapd ;(

2023-05-23 17:39:55.398 [ERR] GRDN: unable to advance state machine: unable to advance state machine: unable to fund psbt: unable to fund psbt: rpc error: code = Unknown desc = wallet couldn't fund PSBT: error creating funding TX: insufficient funds available to construct transaction

To create the asset, a lower-tier Bitcoin transaction must be issued. This requires a certain amount of Bitcoin (UTXO) in the wallet.

Issue an address in lnd and deposit Bitcoin from “somewhere” in “sufficient amount”.

$ lncli -network=testnet newaddress p2tr
{
"address": "tb1pvs0jjwxal4deezmvc0zk7w89n3d44m6f4a3fn3xfxne7as7a9qhq86gj0v"
}

Bitcoin (Testnet) can be free from a faucet such as Bitcoin Testnet Faucet. To issue a Bitcoin transaction containing an asset requires (in the current tapd implementation) an output of 1000 sats, but the transaction fee maybe 10 times that or more.

$ lncli -network=testnet walletbalance
{
"total_balance": "100000",
...
}

This balance would be enough, but collecting this much is a little hard. tapd also supports simnet (signet), so you may consider using that.

Reboot tapd, and the asset appears in the list (not yet created).

$ tapcli assets list
{
"assets": [
{
"version": 0,
"asset_genesis": {
"genesis_point": "ba779153a792a1d49433fd18e56311f8d212992e7d1405cb14af8dffb34e88ce:0",
"name": "fantasycoin",
"meta_hash": "04e552053fd4c8e2c01bc14cb9a0ce00f07d4ffdffff68fe455c70b934b22a43",
"asset_id": "20cecdb6626705bf26ad036084f7423082db6114f0de84046ebf2e84f6852874",
"output_index": 0,
"version": 0
},
"asset_type": "NORMAL",
"amount": "100",
"lock_time": 0,
"relative_lock_time": 0,
"script_version": 0,
"script_key": "02b4c71447e74672f8cd5b50a6b430fc73b3caff7866dc905a502fe8adefad3b31",
"script_key_is_local": true,
"asset_group": null,
"chain_anchor": {
"anchor_tx": "02000000000101ce884eb3ff8daf14cb05147d2e9912d2f81163e518fd3394d4a192a7539177ba0000000000ffffffff02e8030000000000002251203ad594d71881fe6b0e35d2e6e40dd9ee67eac616ee38e65aff7fe7e7283af03372640100000000002251206e83207da3b36b5f8e84a55171e2ac6f21d12c5eb0384a157a9bf927fd88bc6401409722a654cd08582fe39fcbe52be917b2bcdf281ad5244bf058562cea07155a7e75ee68dc4be21842296392c22cc7580a6e118d9b38944014667e69455efe10f200000000",
"anchor_txid": "ebe73fb60dfa99d191ed1e43a0509cc93c5223fa202656c469e01d6abfd66356",
"anchor_block_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"anchor_outpoint": "ebe73fb60dfa99d191ed1e43a0509cc93c5223fa202656c469e01d6abfd66356:0",
"internal_key": "03d9f42daae1b7832d77d3ec83ddbb62e71266f6aedf6bcceb944e9672177c9301",
"merkle_root": "634ff6d86b8889f119f505a9bcba38fe4c6bda4b5a40a439fce37184badff63f",
"tapscript_sibling": null
},
"prev_witnesses": [
],
"is_spent": false
}
]
}

You will understand most of the elements displayed here if you understand the specs.

Bitcoin UTXO (ba779153a792a1d49433fd18e56311f8d212992e7d1405cb14af8dffb34e88ce:0) spent in this asset creation is the genesis_point to identify the asset uniquely.

The new Bitcoin UTXO (anchor_outpoint) of the new Bitcoin transaction (
ebe73fb60dfa99d191ed1e43a0509cc93c5223fa202656c469e01d6abfd66356) that spends this transaction contains the created asset.

The asset creation is complete when this new transaction is confirmed on the chain.

4. Transfer Asset

This time, both sender and receiver use the same lnd / tapd so that you will send it to yourself.

If assets are sent to different tapd, the recipient must first synchronize the sender’s asset creation information using tapcli universe sync.

Create an address to receive 21 units by specifying the asset_id contained in the result of the tapcli asset list described earlier.

$ tapcli addrs new --asset_id 20cecdb6626705bf26ad036084f7423082db6114f0de84046ebf2e84f6852874 --amt 21
{
"encoded": "taptb1qqqsqq3qyr8vmdnzvuzm7f4dqdsgfa6zxzpdkcg57r0ggprwhuhgfa599p6qgggzy56hls2gc3qlh4w9affnl4krx6rxj7t8588ccjfk9pug556lgv7qvggr8epd00wrc5tpctwygpn8kr60efxy5lfj6yl04sf2wxv5v5dsfr8qsqg4nd4u85",
"asset_id": "20cecdb6626705bf26ad036084f7423082db6114f0de84046ebf2e84f6852874",
"asset_type": "NORMAL",
"amount": "21",
"group_key": null,
"script_key": "0225357fc148c441fbd5c5ea533fd6c33686697967a1cf8c493628788a535f433c",
"internal_key": "033e42d7bdc3c5161c2dc440667b0f4fca4c4a7d32d13efac12a71994651b048ce",
"tapscript_sibling": null,
"taproot_output_key": "be0db94139918a132eb4821f834efdec4634261997b57d964753d60923be9dee"
}

Send the asset to the address.

$ tapcli assets send --addr taptb1qqqsqq3qyr8vmdnzvuzm7f4dqdsgfa6zxzpdkcg57r0ggprwhuhgfa599p6qgggzy56hls2gc3qlh4w9affnl4krx6rxj7t8588ccjfk9pug556lgv7qvggr8epd00wrc5tpctwygpn8kr60efxy5lfj6yl04sf2wxv5v5dsfr8qsqg4nd4u85
{
"transfer": {
"transfer_timestamp": "1684836471",
"anchor_tx_hash": "e4efa1c3272009193e961f383b29c1bc84cf6ed8eb0806bf94056a41387835b3",
"anchor_tx_height_hint": 2434958,
"anchor_tx_chain_fees": "12725",
"inputs": [
{
"anchor_point": "ebe73fb60dfa99d191ed1e43a0509cc93c5223fa202656c469e01d6abfd66356:0",
"asset_id": "20cecdb6626705bf26ad036084f7423082db6114f0de84046ebf2e84f6852874",
"script_key": "02b4c71447e74672f8cd5b50a6b430fc73b3caff7866dc905a502fe8adefad3b31",
"amount": "100"
}
],
"outputs": [
{
"anchor": {
"outpoint": "b3357838416a0594bf0608ebd86ecf84bcc1293b381f963e19092027c3a1efe4:0",
"value": "1000",
"internal_key": "024a3cb06616bb1545d3a25417a3fa5ccc70c5fbe9ceed8666410ed83745bbe968",
"taproot_asset_root": "42ac8c2338032a0b0ea9b96916da31a8798eef30cbef2a80b8c6d60249e4698d",
"merkle_root": "42ac8c2338032a0b0ea9b96916da31a8798eef30cbef2a80b8c6d60249e4698d",
"tapscript_sibling": null,
"num_passive_assets": 0
},
"script_key": "02258420ed4cf219965908102c6f8498da274c251a3463880763ba118c2d946c62",
"script_key_is_local": true,
"amount": "79",
"new_proof_blob": "00245663d6bf6a1de069c4562620fa23523cc99c50a0431eed91d199fa0db63fe7eb000000000150000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000096e88000000000000000002fd0163020000000001025663d6bf6a1de069c4562620fa23523cc99c50a0431eed91d199fa0db63fe7eb0100000000ffffffff5663d6bf6a1de069c4562620fa23523cc99c50a0431eed91d199fa0db63fe7eb00000000000000000003e803000000000000225120f036a7c3d36d4459fc4566875b255b80177fee26ea8cb27a1387e38dedee46f8e803000000000000225120be0db94139918a132eb4821f834efdec4634261997b57d964753d60923be9deed52e010000000000225120a0330b1c1bec571ac4e9004cae724380b3acf96a2f88332f4d6bd7136518932c0140db4f7d819f7fe90677c6e0d8355fe2b41f123934a5fb34117377841d3f1f305a82fbe7feea5fb51c2b8f598934027f455fb5d5f12ec6ba5b4538ef8cead916d801407fa2c953e2cbfd8ab9813b06f11919bfc3fba51ef9bac960ae522f9ea2b2f3acfd976dcc31bb49b9c4dc906952ddf7b79c7467a0c1f22ed1d845794adbe558e50000000003010004fd01600001000155ce884eb3ff8daf14cb05147d2e9912d2f81163e518fd3394d4a192a7539177ba000000000b66616e74617379636f696e04e552053fd4c8e2c01bc14cb9a0ce00f07d4ffdffff68fe455c70b934b22a43000000000002010003014f06ad01ab00655663d6bf6a1de069c4562620fa23523cc99c50a0431eed91d199fa0db63fe7eb0000000020cecdb6626705bf26ad036084f7423082db6114f0de84046ebf2e84f685287402b4c71447e74672f8cd5b50a6b430fc73b3caff7866dc905a502fe8adefad3b3101420140013a3f644a0428e3713e8f325b31734ce071fee2a55e3a64a4debd4ef384ba5b340f6b3b9163c5ae37f97aa3a287fd4f1850a3f2a08166568d1a24b2fbda4ece0728fdee0a27d560e5223b5e06b7a61d3df5b405942d21cf887fc96b16088874546a000000000000006408020000092102258420ed4cf219965908102c6f8498da274c251a3463880763ba118c2d946c62059f0004000000000121024a3cb06616bb1545d3a25417a3fa5ccc70c5fbe9ceed8666410ed83745bbe96802740049000100012020cecdb6626705bf26ad036084f7423082db6114f0de84046ebf2e84f685287402220000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff012700010001220000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff06f802c70004000000010121033e42d7bdc3c5161c2dc440667b0f4fca4c4a7d32d13efac12a71994651b048ce029c0071000100012020cecdb6626705bf26ad036084f7423082db6114f0de84046ebf2e84f6852874024a0001c37d7cb2bb159ad5843a204c210782c937765e0c54e1ac431f5519d0b02b95410000000000000015ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbf012700010001220000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2e0004000000020121026c93d33b5f69084411ffc540db8c0135300fdb4cdae2e5cd6ad1efd27a9d8cc20303020101",
"split_commit_root_hash": "fdee0a27d560e5223b5e06b7a61d3df5b405942d21cf887fc96b16088874546a",
"output_type": "OUTPUT_TYPE_SPLIT_ROOT"
},
{
"anchor": {
"outpoint": "b3357838416a0594bf0608ebd86ecf84bcc1293b381f963e19092027c3a1efe4:1",
"value": "1000",
"internal_key": "033e42d7bdc3c5161c2dc440667b0f4fca4c4a7d32d13efac12a71994651b048ce",
"taproot_asset_root": "8aada842f74c2b11e9b5a0a716baa0c453855ab6ea316222a8a28e7e79506f41",
"merkle_root": "8aada842f74c2b11e9b5a0a716baa0c453855ab6ea316222a8a28e7e79506f41",
"tapscript_sibling": null,
"num_passive_assets": 0
},
"script_key": "0225357fc148c441fbd5c5ea533fd6c33686697967a1cf8c493628788a535f433c",
"script_key_is_local": true,
"amount": "21",
"new_proof_blob": "00245663d6bf6a1de069c4562620fa23523cc99c50a0431eed91d199fa0db63fe7eb000000000150000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000096e88000000000000000002fd0163020000000001025663d6bf6a1de069c4562620fa23523cc99c50a0431eed91d199fa0db63fe7eb0100000000ffffffff5663d6bf6a1de069c4562620fa23523cc99c50a0431eed91d199fa0db63fe7eb00000000000000000003e803000000000000225120f036a7c3d36d4459fc4566875b255b80177fee26ea8cb27a1387e38dedee46f8e803000000000000225120be0db94139918a132eb4821f834efdec4634261997b57d964753d60923be9deed52e010000000000225120a0330b1c1bec571ac4e9004cae724380b3acf96a2f88332f4d6bd7136518932c0140db4f7d819f7fe90677c6e0d8355fe2b41f123934a5fb34117377841d3f1f305a82fbe7feea5fb51c2b8f598934027f455fb5d5f12ec6ba5b4538ef8cead916d801407fa2c953e2cbfd8ab9813b06f11919bfc3fba51ef9bac960ae522f9ea2b2f3acfd976dcc31bb49b9c4dc906952ddf7b79c7467a0c1f22ed1d845794adbe558e50000000003010004fd02a80001000155ce884eb3ff8daf14cb05147d2e9912d2f81163e518fd3394d4a192a7539177ba000000000b66616e74617379636f696e04e552053fd4c8e2c01bc14cb9a0ce00f07d4ffdffff68fe455c70b934b22a43000000000002010003011506fd021d01fd02190065000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002fd01ae4a00014d1b83c6f29417ec329b91f9cc95cb38facb112bf1d12d4ca3c45d5cd3765857000000000000004fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffd01600001000155ce884eb3ff8daf14cb05147d2e9912d2f81163e518fd3394d4a192a7539177ba000000000b66616e74617379636f696e04e552053fd4c8e2c01bc14cb9a0ce00f07d4ffdffff68fe455c70b934b22a43000000000002010003014f06ad01ab00655663d6bf6a1de069c4562620fa23523cc99c50a0431eed91d199fa0db63fe7eb0000000020cecdb6626705bf26ad036084f7423082db6114f0de84046ebf2e84f685287402b4c71447e74672f8cd5b50a6b430fc73b3caff7866dc905a502fe8adefad3b3101420140013a3f644a0428e3713e8f325b31734ce071fee2a55e3a64a4debd4ef384ba5b340f6b3b9163c5ae37f97aa3a287fd4f1850a3f2a08166568d1a24b2fbda4ece0728fdee0a27d560e5223b5e06b7a61d3df5b405942d21cf887fc96b16088874546a000000000000006408020000092102258420ed4cf219965908102c6f8498da274c251a3463880763ba118c2d946c620802000009210225357fc148c441fbd5c5ea533fd6c33686697967a1cf8c493628788a535f433c059f0004000000010121033e42d7bdc3c5161c2dc440667b0f4fca4c4a7d32d13efac12a71994651b048ce02740049000100012020cecdb6626705bf26ad036084f7423082db6114f0de84046ebf2e84f685287402220000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff012700010001220000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff06f802c70004000000000121024a3cb06616bb1545d3a25417a3fa5ccc70c5fbe9ceed8666410ed83745bbe968029c0071000100012020cecdb6626705bf26ad036084f7423082db6114f0de84046ebf2e84f6852874024a0001979a9f1a4fc2545215daab7876c5e47907a9ff6ed4cd0ca7dcedb6cf3eafc50c000000000000004fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbf012700010001220000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2e0004000000020121026c93d33b5f69084411ffc540db8c0135300fdb4cdae2e5cd6ad1efd27a9d8cc20303020101079f0004000000000121024a3cb06616bb1545d3a25417a3fa5ccc70c5fbe9ceed8666410ed83745bbe96802740049000100012020cecdb6626705bf26ad036084f7423082db6114f0de84046ebf2e84f685287402220000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff012700010001220000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"split_commit_root_hash": null,
"output_type": "OUTPUT_TYPE_SIMPLE"
}
]
}
}

A new Bitcoin transaction (b3357838416a0594bf0608ebd86ecf84bcc1293b381f963e19092027c3a1efe4) was issued that spent the Bitcoin UTXO (ebe73fb60dfa99d191ed1e43a0509cc93c5223fa202656c469e01d6abfd66356:0) just described, including the created asset.

Finally, I summarize the Bitcoin transactions, their UTXOs, and changes. Assets do not decrease with each transfer and remain the same before and after the transfer, while Bitcoin (sats) decrease for the transaction fee.

--

--