Register a BNS Name

A Runbook can be used to easily register a new BNS name. Follow along with this guide to build a BNS Runbook and to practice some Runbook basics.

Project Setup

To set up the project, we will use the txtx new command. Run the command and fill out the following data:

> txtx new
Enter the name of this workspace: Txtx Guide
✔ Choose a Runbook type: · Maintenance: update settings, authorize new contracts, etc.
✔ Enter a name for the runbook (e.g., 'BNS Multisig') · Register BNS
✔ Enter the description for the runbook (optional) · Registers a BNS address on the Stacks blockchain
Created manifest txtx.yml
Created file runbooks/README.md
Created runbook runbooks/maintenance/register-bns.tx

Thie will generate the following manifest:

---
name: Txtx Guide
id: txtx-guide
runbooks:
  - name: Register BNS
    id: register-bns
    description: Registers a BNS address on the Stacks blockchain
    location: runbooks/maintenance/register-bns.tx
environments:
  devnet:
    stacks_network_id: devnet
    stacks_api_url: http://localhost:3999
    stacks_operator_address: ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC
  testnet:
    stacks_network_id: testnet
    stacks_api_url: https://api.testnet.hiro.so
    stacks_operator_address: ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC
  mainnet:
    stacks_network_id: mainnet
    stacks_api_url: https://api.hiro.so
    stacks_operator_address: SP2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1J5QKA2F

While this setup will work fine, we'll modify it slightly to keep our Runbook organized. In the runbooks/maintenance/ folder, delete the register-bns.tx file, and replace it with a folder called runbook-bns containing three files:

  • runbooks/maintenance/register-bns/variables.tx
  • runbooks/maintenance/register-bns/main.tx
  • runbooks/maintenance/register-bns/outputs.tx Finally, change the "location" field of the manifest to point to this register-bns folder rather than just the file. When we execute the Runbook, all three files will be loaded. Having these separate files allows us to separate the logic of inputs, outputs, and the on-chain actions that will be run. Note, this is purely for organizational purposes and is completely optional.

Setting Variables

Next, we'll set some variables for our Runbook. Here we can define some wallets that will be used for signing the transactions. We can also set variables that can be configured at runtime to dictate the BNS name, namespace, and some other details needed for registering a BNS name. Note: Because each variable has the editable field set to true, each of these values will be editable in the Web UI. To restrict an input from being editable in the Web UI, remove the editable field, or set it to false.

variables.tx

signer "operator" "stacks::web_wallet" {
  expected_address = input.expected_address
}
variable "namespace" {
  description = "TLD / Namespace"
  value = "btc"
  editable = true
}
variable "name" {
  description = "Domain name to order"
  value = "txtx"
  editable = true
}
variable "salt" {
  description = "Random salt to use for preventing front-running"
  value = "00000000000000"
  editable = true
}
variable "zonefile" {
  description = "Zonefile to attach to the new domain"
  value = "0000000000"
  editable = true
}

Sending Transactions

Once our variables are set up, we can actually send some transactions. We use the addon "stacks" command to set the network_id and rpc_api_url for all Stacks plugin commands. This will prevent these fields from having to be specified for every Stacks plugin command.

addon "stacks" {
  network_id = input.stacks_network_id
  rpc_api_url = input.stacks_api_url
}

To assemble our transactions, we will also need the current price of the BNS name we're registering. We can get this using the stacks::call_readonly_fn command to call a readonly smart contract function which contains the price. The result of this transaction can be parsed and used in subsequent transactions.

action "get_name_price" "stacks::call_readonly_fn" {
  description = "Preorder name"
  contract_id = "ST000000000000000000002AMW42H.bns"
  function_name = "get-name-price"
  function_args = [
      stacks::cv_buff(encode_hex(variable.namespace)),
      stacks::cv_buff(encode_hex(variable.name))
  ]
  sender = "ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC"
}

From there, we can assemble our transaction payloads according to the spec of the BNS contract. We will need two transactions - one to preorder the BNS name and one to register it. Both of these transactions will use the same action, stacks::call_contract. This command allows you to create a transaction payload, sign it, and broadcast it to the network.

action "send_name_preorder" "stacks::call_contract" {
  description = "Send Preorder ${variable.name}.${variable.namespace} transaction"
  contract_id = "ST000000000000000000002AMW42H.bns"
  function_name = "name-preorder"
  function_args = [
      stacks::cv_buff(
        ripemd160(sha256(
          [
            encode_hex("${variable.name}.${variable.namespace}"),
            encode_hex(variable.salt)
          ]
        ))
      ),
      stacks::cv_uint(action.get_name_price.value), 
  ]
  signer = signer.operator
  confirmations = 1
}
action "send_name_register" "stacks::call_contract" {
  description = "Register name"
  contract_id = "ST000000000000000000002AMW42H.bns"
  function_name = "name-register"
  function_args = [
      stacks::cv_buff(encode_hex(variable.namespace)),
      stacks::cv_buff(encode_hex(variable.name)),
      stacks::cv_buff(encode_hex(variable.salt)),
      stacks::cv_buff(encode_hex(variable.zonefile)),
  ]
  signer = signer.operator
  depends_on = [action.send_name_preorder.tx_id]
}

For each of the transactions, we set the signer to reference the signer we created in the inputs file (wallet.operator). Aside from the transaction payload, the only difference between the name preorder and registration transactions is the depends_on field. This field can be specified to create dependencies in the order of transaction execution. Because the BNS registration transaction has a depends_on field referencing the BNS preorder transaction, it won't be executed until the preorder transaction is confirmed. We can put this all together to have our main.tx file:

main.tx

action "defaults" "stacks::set_default_network" {
  network_id = input.stacks_network_id
  rpc_api_url = input.stacks_api_url
}
action "get_name_price" "stacks::call_readonly_fn" {
  description = "Preorder name"
  contract_id = "ST000000000000000000002AMW42H.bns"
  function_name = "get-name-price"
  function_args = [
      stacks::cv_buff(encode_hex(variable.namespace)),
      stacks::cv_buff(encode_hex(variable.name))
  ]
  sender = "ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC"
}
action "send_name_preorder" "stacks::send_contract_call" {
  description = "Send Preorder ${variable.name}.${variable.namespace} transaction"
  contract_id = "ST000000000000000000002AMW42H.bns"
  function_name = "name-preorder"
  function_args = [
      stacks::cv_buff(
        ripemd160(sha256(
          [
            encode_hex("${variable.name}.${variable.namespace}"),
            encode_hex(variable.salt)
          ]
        ))
      ),
      stacks::cv_uint(action.get_name_price.value), 
  ]
  signer = wallet.operator
  confirmations = 1
}
action "send_name_register" "stacks::send_contract_call" {
  description = "Register name"
  contract_id = "ST000000000000000000002AMW42H.bns"
  function_name = "name-register"
  function_args = [
      stacks::cv_buff(encode_hex(variable.namespace)),
      stacks::cv_buff(encode_hex(variable.name)),
      stacks::cv_buff(encode_hex(variable.salt)),
      stacks::cv_buff(encode_hex(variable.zonefile)),
  ]
  signer = wallet.operator
  confirmations = 1
  depends_on = [action.send_name_preorder.tx_id]
}

Displaying Outputs

Finally, we will specify some output commands to display data at the end of the runbook execution. We put this in the outputs.tx file purely for organizational purposes.

outputs.tx

output "bns_name" {
  value = "Registering ${variable.name}.${variable.namespace}"
}
output "name_price" {
  value = action.get_name_price.value
}
output "name_preorder_tx_link" {
  value = "https://explorer.hiro.so/txid/${action.send_name_preorder.tx_id}?chain=${input.stacks_network_id}"
}
output "name_preorder_result" {
  value = action.send_name_preorder.result
}
output "name_register_tx_link" {
  value = "https://explorer.hiro.so/txid/${action.send_name_register.tx_id}?chain=${input.stacks_network_id}"
}
output "name_register_result" {
  value = action.send_name_register.result
}

Executing the Runbook

With txtx installed, run the following command in your terminal:

txtx run register-bns

This will run txtx on http://localhost:8488. Follow the prompts in the Web UI to execute the runbook.

Was this page helpful?