# Trac Network

Trac Network is a new L1 with strict peer-to-peer and self-custodial principles. By enabling every participant to become part of the infrastructure, it allows for fast transactions, finality, and incentives.&#x20;


# How Does It Work?

Trac Network - unlike traditional blockchains, does not utilize blocks to process transactions. Instead it operates in favor of a constant stream of transactions that are peer-to-peer-validated and settled on decentralized ledgers. The topology of Trac Network allows for near-instant transaction settlement and a better user-experience. Trac Network uses DAGs as well as distributed clocks to maintain linearized and causal order of network messages.


# Decentralization

**Decentralization is at the heart of Trac Network**

* Transaction settlement and smart contract execution are separated:
  * Transactions are performed by the Main Settlement Bus (MSB).
  * MSB validators accept transaction requests and sign those.
  * Smart Contract operations are performed by individual nodes in the network (the apps).
  * Consensus is separated between MSB and apps.
  * Apps represent their own network and use their own consensus to settle smart contract operations.


# Cryptography

All transactions are signed and verified using the EdDSA-based ed25519 algorithm. This allows for secure transactions and fast verification throughout the network. Trac Network uses native libsodium libraries to assure the fastest possible results upon execution.


# Transaction Flow

* Apps request an MSB validator to get a transaction for a smart contract operation validated.
* The MSB validator checks the request - and if successful - signs & appends the transaction on the decentralized MSB ledger.
* The app waits for the transaction to get finalized by the MSB consensus and performs the smart contract operation.
* The result of the operation is signed & appended on the app's decentralized ledger if the consensus agrees on the outcome.


# Consensus

Consensus is reached based on (double-proof) 51% majority for transactions as well as smart contract operations.

Consensus nodes are independent and called indexers. MSB and apps each utilize their own set of indexers. Aside from consensus, indexers keep a full index and take care of the causal order of network messages.&#x20;

Any other node is called writer or reader and operates sparse: only the data of interest is being shared and used across the network.


# Terminology

**Trac Network vs. Blockchains Terminology Comparison**

Main Settlement Bus (MSB) ⇒ Transaction ledger

Apps ⇒ Smart Contract ledgers

MSB and Apps may be used as:

Indexers ⇒ Archive nodes (consensus, index)

Writers ⇒ Nodes (validators : full nodes, apps: light nodes)

Readers ⇒ Read-only nodes (statistics, 3rd party applications)


# Transaction Performance

The performance numbers below are determined using a defensive network setup. Trac Network will over time increase the setup boundaries to carefully get along with performance tweaks and demand.

**Theoretical TPS:** apps \* validators \* 1000

**TPS:** apps \* 1000

**TX Finality:** 100 / s

**Bottleneck:** TX Finality / s may increase linear with more than 100 tx / s

Unlike with traditional blockchain statistics, we see TX Finality as the most important metric and favor it over raw TPS.


# Reorgs

Reorgs (short for reorginzations) are a typical task to achieve consensus for decentralized ledgers. Trac Network is no exception. However, since there are no blocks, only individual transactions may be reorganized. Once finalized, those are guaranteed to not change anymore. As stated previously, in the current defensive network setup, 100 tx / s are being finalized, delivering a near-instant experience to users depending on the network traffic.


# Transaction Fees

This section describes fee distribution.

## Constants

* `FEE` = 0.03 $TNK base fee.

## Operations (excluding TRANSFER and TX)

| Operation             | Who pays / amount           | When charged                            | Validator                              | Remainder                              |
| --------------------- | --------------------------- | --------------------------------------- | -------------------------------------- | -------------------------------------- |
| AddAdmin              | Admin, `0`                  | One-time during initialization          | 0%                                     | n/a (free)                             |
| BalanceInitialization | Admin (sender), `0`         | While initialization flag is enabled    | 0%                                     | n/a (free)                             |
| AppendWhitelist       | Admin, `FEE`                | Only if initialization flag is disabled | 0%                                     | 100% burned (deducted from admin)      |
| AddWriter             | Writer candidate, `FEE`     | Always charged                          | 75%                                    | 25% burned                             |
| RemoveWriter          | Writer being removed, `FEE` | Always charged                          | 75%                                    | 25% burned                             |
| AdminRecovery         | Admin, `FEE`                | Always charged                          | 75%                                    | 25% burned                             |
| AddIndexer            | Admin, `FEE`                | Always charged                          | 0%                                     | 100% burned                            |
| RemoveIndexer         | Admin, `FEE`                | Always charged                          | 0%                                     | 100% burned                            |
| BanValidator          | Admin, `FEE`                | Always charged                          | 0%                                     | 100% burned                            |
| BootstrapDeployment   | Deployment initiator, `FEE` | Always charged                          | 75%                                    | 25% burned                             |
| Transfer              | Requester, `FEE`            | Always charged                          | 75%                                    | 25% burned                             |
| TX                    | Requester, `FEE`            | Always charged                          | depends on subnet owner (see TX cases) | depends on subnet owner (see TX cases) |

### Operation details

#### AddAdmin

* Payer: admin (network creator), amount: free.
* Effect: admin receives initial balance `1000 $TNK` and initial staked balance `0.3 $TNK`; admin is set as indexer+validator+whitelisted and admin entry is created. Runs exactly once during bootstrap.

#### AddWriter

* Payer: writer candidate, amount: `FEE`.
* Split: validator 75%, burned 25%.
* Effect: role set to writer, staking updated, key registered.

#### RemoveWriter

* Payer: writer being removed, amount: `FEE`.
* Split: validator 75%, burned 25%.
* Effect: role set to whitelisted, stake released, writer key unregistered.

#### AdminRecovery

* Payer: admin rotating writer key, amount: `FEE`.
* Split: validator 75%, burned 25%.
* Effect: admin writer key swapped, indexer entry updated to the new key.

#### BootstrapDeployment

* Payer: deployment initiator, amount: `FEE`.
* Split: validator 75%, burned 25%.
* Effect: deployment entry stored for the bootstrap.

## Transfer (OperationType.TRANSFER)

* Payer: requester (sender).
* Fee: `FEE` (always charged, in self-transfer only the fee is deducted).
* Basics:
  * Amount deducted from sender: `transferAmount + FEE`. In self-transfer only the fee is deducted.
  * If the sender lacks full funds for the deduction, the operation is ignored (state unchanged).
* Fee split: validator 75%, burned 25%.
* Transfer amount to recipient:
  * Recipient is not a validator: recipient gets `transferAmount` (can be 0). A new recipient is initialized as READER with that balance.
  * Recipient is the validator: validator gets `transferAmount` plus its fee share (75% of `FEE`).
  * Self-transfer: recipient equals sender. Recipient balance is unchanged. Fee still goes 75% to validator, 25% burned.

## Subnetwork TX (OperationType.TX)

* Payer: requester.
* Fee: `FEE`.
* Fee split depends on who deployed the subnet (`bootstrapDeployer`):
  1. `bootstrapDeployer = requester`, validator is different:
     * Validator: 50%
     * Bootstrap deployer: 0% (no discount for owning the subnet)
     * Burned: 50%
  2. `bootstrapDeployer = validator`, requester is different:
     * Validator (and deployer): 75% (50% as validator + 25% as deployer)
     * Burned: 25%
     * Requester: 0%
  3. `bootstrapDeployer` is neither requester nor validator:
     * Validator: 50%
     * Bootstrap deployer: 25%
     * Burned: 25%
     * Requester: 0%
* The fee is deducted from the requester before distribution. Others receive only the shares above.

## Zero-fee operations

* `ADD_ADMIN` (one-time bootstrap, assigns `1000 $TNK` liquid + `0.3 $TNK` staked to admin).
* `BALANCE_INITIALIZATION` (sender -> recipient top-up) is free while initialization is enabled.
* `DISABLE_INITIALIZATION` is free, can be used exactly once to turn off balance initialization.
* AppendWhitelist before initialization is disabled is free (fee appears only after the flag is turned off).

## Validator penalties

* For a batch with invalid operations or an oversized batch: `penalty = FEE * invalidOperations`.
* The penalty is taken from staked balance, not distributed, and the validator is downgraded to whitelisted. Remaining stake after the penalty returns to the normal balance, so part of the penalty is effectively burned.
* Each failed operation in a batch (validation errors, unmet conditions, invalid payload) increases the invalid counter and triggers at least one `FEE` worth of penalty.

## Note on the `bs` field in TX

* `bs` identifies the subnet bootstrap, mapped in MSB to the subnet creator address.
* A TX is valid only if `bs` points to a registered bootstrap.
* The subnet owner (address tied to `bs`) receives a share of the TX fee only when they are not the requester. If requester or validator is the creator, the creator’s share is folded into their respective percentage as described above.


# Inflation/Deflation Rate

Validators are rewarded with $TNK that are individually mined per validator and per the transaction they process.

Combined with the burning mechanism, we are targeting an inflation/deflation rate of max 2% / year.

Since this is under development, we will update this part of the documentation accordingly.


# Why Flat Fees?

Since the MSB is only responsible for processing and storing transactions but not for smart contract execution, there is no need to increase the network fee. Instead, contract deployers are rewarded for transactions going through their contracts into the MSB as they are part of the infrastructure.

Burning of $TNK will increase its price against other assets like stables, thus utilizing common market rules (supply/demand) as natural way to control the tx rate.


# The Reward & Compete Principle

Since the goal of the Trac Network is to become the fastest finalizing Crypto network, it is of upmost importance that validators and app deployers provide sufficient performance:

* Their nodes must have as little downtimes as possible.
* Their network connectivity must be sufficient.

To achieve this, the rewards are generous but are only granted under the rules of competition:

* Apps & users may choose validators individually if necessary to get transactions validated.
  * This is akin to broadcast transactions into the Mempool of specific mining pools of Bitcoin for example.
  * Leads to validators trying to achieve the best performance and compete with each other over availability and quality.
  * Individual license identities can be grouped into pools, opening market opportunities for validator pools.
  * ⇒ Creates an open and fair validator-to-user (b2c) market.
* Validators may decide to help validating an app network and compete with each other over the most successful apps for earning additional rewards.
* App deployers may ask the best validators to help validating their apps in return for further rewards.
* ⇒ Creates an open validator-to-deployer (b2b) market and unlocks more incentives.
* Users may choose an app over their overall availability and quality, as well and contribute heavily to the success of an app.
* ⇒ Creates a true consumer market on app level (b2c).


# Validators

Validators are cruicial for the Trac Network because they validate and verify incoming transactions from users of apps into the Main Settlement Bus. Those transactions signal the smart contracts of apps to process operations.

This section answers the following questions:

* What are the requirements to become a validator?
* What are the hardware specs?
* What software to install and use to operate a validator?
* How to earn $trac and other assets? ($trac available on mainnet release)


# Requirements

To become a validator, a license needs to be obtained. There is a maximum of 3000 licenses available that are being distributed in several rounds. Please follow us [on X ](https://x.com/trac_btc)or join our [Discord](https://discord.com/invite/trac) to watch out for new rounds to be announced.


# Hardware Specs

**Operating Systems**

* Linux
* Windows
* MacOS

**Minimum Hardware**

* For max. 2 validators
* 4 - 8 Cores
* 8-16GB Ram
* 250 - 500 GB SSD (NVMe preferred)
* Peer-to-peer friendly home ISP

**Recommended Hardware**

* For max. 4 validators
* 8+ cores
* 16GB+ Ram
* 500 GB NVMe
* Data Center / 1Gbit conn.
* Metal is best, VMs work if performance guaranteed


# Installation

Validators can be installed and used for different "flavours" of the Network. As a first release, Trac Network starts with a specific app called Hypermall - a fast DEX for Bitcoin assets on the [TAP Protocol](https://tap-protocol.com).

Please continue with the validator setup for Hypermall if you intend to make use of your validator license and benefit from trading fees.

Note: Upon Trac Network mainnet release, Trac turns into a multi-purpose network and validators will be rewarded in $TNK for transactions they validate.


# Mainnet

Validators install and use instances of the Main Settlement Bus (MSB). Learn how to install & operate the MSB for Trac Network Mainnet on Windows, MacOS and Linux.

First we need to install **Node.js**. If you are not tech-savvy, don't worry, it's straight-forward. If you already have Node installed, you can skip its installation and move straight to the Pear installation.

Important: Do NOT apply the below inside trac-msb folders if you previously created a validator for Hypermall. Follow the docs below on what folder names to use as suggestion so there won't be no collision.

### **Windows**

Open a PowerShell (right click > run as admin) and enter:

```powershell
# allow script execution
# if you are an admin, you may use more restricted settings based on your dev op rules
Set-ExecutionPolicy Unrestricted

# Download and install fnm:
winget install Schniz.fnm

# set some environment options
fnm env --use-on-cd | Out-String | Invoke-Expression

# Download and install Node.js:
fnm install 22

# Verify the Node.js version:
node -v # Should print a version like "v22.14.0".

# Verify npm version:
npm -v # Should print a version like "10.9.2".
```

### **MacOS/Linux**

Open a terminal/console and enter:

```bash
# Download and install nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash

# in lieu of restarting the shell
\. "$HOME/.nvm/nvm.sh"

# Download and install Node.js:
nvm install 22

# Verify the Node.js version:
node -v # Should print a version like "v22.15.0".

nvm current # Should print a version like "v22.15.0".

# Verify npm version:
npm -v # Should print a version like "10.9.2".
```

Once you confirmed the commands "node -v" and "npm -v" return no error but print a version number,  we can proceed.\
Should curl for some reason not exist, then install it first. If it is missing on MacOS, you can try "/usr/bin/curl" instead of just "curl".

### **Installing Pear Runtime**

Next is installing the **Pear Runtime**. The MSB runs on top of it and allows for efficient MSB execution. In principle running the MSB using plain Node works, but it is recommended to use Pear.

Simply execute the following in your shell / console / terminal:

```bash
npm i -g pear
pear -v
```

If pear asks you to set the paths after installation, please execute the "export" command that is provided.

"pear -v" should print the current version that you installed and you're ready to continue installing the MSB. It is important that you use the exact version of the MSB as this is the final release that everyone running an MSB must be operating on:

**Windows**

In your file explorer, create an empty directory called "my-validator". Then use the PowerShell to enter it:

```powershell
cd my-validator
```

**Linux / MacOS**

Create an empty directory using the console / terminal and enter it:

```bash
# create
mkdir my-validator

# enter the new folder
cd my-validator
```

### **Install/Update the MSB Validator**

You may now choose between decentralized MSB distribution or manual install.

Updates works like this:&#x20;

* Re-install the latest version (latest are always the versions below)
* Start with the exact store name(s) that you used upon install and first run (e.g. store1, store2, etc)
* Type "/add\_writer" and hit enter in the validator screen to make sure the network handles your update quicker
* Skip the onboarding and post-install process below entirely
* Note: If it asks you for a new seedphrase, you might have not choosen the correct store name upon restart

**Decentralized**

To run the MSB, execute the following command:

```bash
pear run pear://6rpmo1bsedagn4u56a85nkzkrxcibab53d7sgds7ukn6kfyzgiwy store1
```

You will be asked to enter the word "TRUST" once. Please do so and once done, follow the instructions and simply repeat the above command. You can skip the below and continue from the "Post Install" section.&#x20;

**Manual**

Now install the MSB exactly like this (the version number is important):

```bash
npm install trac-msb@0.2.2
```

If the installation finished without errors, we can start the MSB itself.

### **Starting the MSB**

Copy the MSB, as Pear doesn't like to be run in node\_modules:

**Windows:**

Use your file explorer and copy all contents from the folder "my-validator/node\_modules/trac-msb/" into "my-validator" (override existing files).

**Linux/MacOS**

```bash
cp -fr node_modules/trac-msb/* .
```

Then proceed (Windows/Linux/MacOS):

```bash
# pull the dependencies
npm install

# run the msb using pear
npm run prod --store=store1
```

**Note: In rare cases, there is a chance nothing appears to happen. In this case, please kill all "pear-runtime" processes using the process manager of your operating system. Then start again. A bug report has been sent to the pear team.**

### **Post Install**

"pear run" starts the MSB in the current directory "." and "store1" is the name of the database (located in stores/store1). The database also contains a backup of your identity.&#x20;

**Note:** To run multiple instances, simply use a different store name than "store1". You will need to run multiple instances if you own more than one license, because each validator has its associated identity.

After the MSB started the first time without identity, you should see the following information:

<figure><img src="/files/RtVajve0l35QSwtY2eVr" alt=""><figcaption></figcaption></figure>

Please type `1` and hit enter and show you the seed phrase.

**Windows**

**Linux / MacOS**

```bash
cp stores/store1/db/keypair.json /some/backup/location/
```

The keypair.json file contains the seed phrase that you can use to restore your identity in a new installation.

The MSB has been fully started if you something like this (isWriter: false and balance 0 on new setups):

<figure><img src="/files/8fcfhYgP5la4KuQVXoYr" alt=""><figcaption></figcaption></figure>

To stop your MSB, enter "/exit" (use the CTRL + C keys on your keyboard only as last resort).&#x20;

To restart use the exact same start parameters you used earlier from within your MSB installation folder.

### **Getting Whitelisted**

We are assuming that your MSB instance (your MSB Address) isn't whitelisted yet. In order to validate your license, keep your Bitcoin wallet at hand and navigate to the [public onboarding site](https://onboarding.tracvalidator.com/).&#x20;

<mark style="color:red;">**Important:**</mark> If you previously performed your whitelist for Hypermall already, you will need to do this step once more but this time for the actual MSB Address as of your validator setup above. MSB addresses start with "trac1...". Hypermall on R1 use raw public keys instead of streamlined Trac Network addresses.

Follow the instructions to swap your licenses on Bitcoin to your Trac Network identities (the MSB Addresses).

You will need a minimal amount of $TNK to complete the whitelist (< 1 $TNK). If you do not own any $TNK, then please ask the team in the Trac Network [Discord](https://discord.gg/trac) to fund the initial amount for your MSB Address.

Once received, you will be added to the whitelist after some processing time (up to 24 hours). Once added, please  restart your MSB, type  the command "/add\_writer" and hit enter. After a few seconds you should see a confirmation about your MSB being added.&#x20;

From that moment on, every transaction that your MSB validates will add to your fee balance for your MSB Address. You can use the built-in wallet features to get balances and perform transfers.

As mentioned above, for multiple licenses, please repeat the process of starting the MSB with different stores as each validator is supposed to have its own identity.

<mark style="color:red;">**Important:**</mark>**&#x20;Never run the same store twice as you would try to join the network 2 or more times with the same identity and cause "chatter" (permanent logins and logouts). To avoid reconnecting issues, make sure to run each store only once.**

### **Running the MSB in Background**

There are several ways to run the MSB in the background. For portability and cross-operating-system support, we recommend to use the Node package "pm2". [See here](https://pm2.io/docs/runtime/guide/installation/) on how to install (using the "npm" method is fine).

Once installed, start the MSB like this:

```bash
pm2 start pear --name "My MSB #1" -- run . store1
```

<mark style="color:red;">**Important**</mark>**: If you use PM2, please start your instances using the "--no-autorestart" flag, otherwise you may run into issues.**

Check the pm2 documentation for further details as it is an extremely powerful tool to manage multiple instances of programs that are supposed to run in the background.

### **Congratulations!**

You just learned how to install and use an MSB and become a validator!

You can use /get\_balance \<MSB Address> and /transfer to check your earnings and transfer funds from within your validator interface now!

Never, ever, remove all your TNK funds from your validator wallet, leave at least 0.33 TNK in it. The minimum amount is a network safety measure.


# Testnet

Validators install and use instances of the Main Settlement Bus (MSB). Learn how to install & operate the MSB for Trac Network Testnet on Windows, MacOS and Linux.

First we need to install **Node.js**. If you are not tech-savvy, don't worry, it's straight-forward. If you already have Node installed, you can skip its installation and move straight to the Pear installation.

Important: Do NOT apply the below inside trac-msb folders if you previously created a validator for Hypermall. Follow the docs below on what folder names to use as suggestion so there won't be no collision.

### **Prerequisites**

&#x20;\- On Windows you should install `git` . Please follow offical [documentation](https://git-scm.com/install/windows).

### **Windows**

Open a PowerShell (right click > run as admin) and enter:

```powershell
# allow script execution
# if you are an admin, you may use more restricted settings based on your dev op rules
Set-ExecutionPolicy Unrestricted

# Download and install fnm:
winget install Schniz.fnm

# set some environment options
fnm env --use-on-cd | Out-String | Invoke-Expression

# Download and install Node.js:
fnm install 22

# Verify the Node.js version:
node -v # Should print a version like "v22.14.0".

# Verify npm version:
npm -v # Should print a version like "10.9.2".
```

### **MacOS/Linux**

Open a terminal/console and enter:

```bash
# Download and install nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash

# in lieu of restarting the shell
\. "$HOME/.nvm/nvm.sh"

# Download and install Node.js:
nvm install 22

# Verify the Node.js version:
node -v # Should print a version like "v22.15.0".

nvm current # Should print a version like "v22.15.0".

# Verify npm version:
npm -v # Should print a version like "10.9.2".
```

Once you confirmed the commands "node -v" and "npm -v" return no error but print a version number,  we can proceed.\
Should curl for some reason not exist, then install it first. If it is missing on MacOS, you can try "/usr/bin/curl" instead of just "curl".

### **Installing Pear Runtime**

Next is installing the **Pear Runtime**. The MSB runs on top of it and allows for efficient MSB execution. In principle running the MSB using plain Node works, but it is recommended to use Pear.

Simply execute the following in your shell / console / terminal:

```bash
npm i -g pear
pear -v
```

If pear asks you to set the paths after installation, please execute the "export" command that is provided.

"pear -v" should print the current version that you installed and you're ready to continue installing the MSB. It is important that you use the exact version of the MSB as this is the final release that everyone running an MSB must be operating on:

### **Install Testnet MSB Validator**

Run the following commands:

```
git clone https://github.com/Trac-Systems/main_settlement_bus.git msb-testnet
cd msb-testnet
git fetch --tags
git checkout -B 0.2.17 v0.2.17
npm install
npm run start -- --stores-directory ./experimental_store --network testnet
```

### **Post Install**

`npm run start` starts the MSB in the current directory `.` and `experimental_store` is the name of the database (located in `experimental_store/testnet`). The database also contains a backup of your identity.&#x20;

**Note:** To run multiple instances, simply use a different store name than `experimental_store`. You will need to run multiple instances if you own more than one license, because each validator has its associated identity.

After the MSB started the first time without identity, you should see the following information:

<figure><img src="/files/M38nukTasibz2iMIgSGc" alt=""><figcaption></figcaption></figure>

Please type `1` and hit enter and show you the seed phrase.

The MSB has been fully started if you something like this (`isWriter`: false and balance `0` on new setups):

<figure><img src="/files/fxvstcSRutNIeKx6JG4p" alt=""><figcaption></figcaption></figure>

o stop your MSB, enter "/exit" (use the CTRL + C keys on your keyboard only as last resort).&#x20;

To restart use the exact same start parameters you used earlier from within your MSB installation folder.

### **Getting Whitelisted**

We are assuming that your MSB instance (your MSB Address) isn't whitelisted yet. You will need a minimal amount of $tTNK to complete the whitelist. You can fund your testnet address (highlighted) with our [faucet](https://faucet.trac.network/).\
\
You can find your transactions on the [testnet explorer](https://testnet.trac.network/). **TAP wallet** supports testnet.\
\
If you want to become TESTNET validator, then please ask then please ask the team in the Trac Network [Discord](https://discord.gg/trac).<br>

Once received, you will be added to the whitelist after some processing time (up to 24 hours). Once added, type  the command `/add_writer` and hit enter. After a few seconds you should see a confirmation about your MSB being added.&#x20;

From that moment on, every transaction that your MSB validates will add to your fee balance for your MSB Address. You can use the built-in wallet features to get balances and perform transfers.

<mark style="color:red;">**Important:**</mark>**&#x20;Never USE YOUR MAINNET private keys on TESTNET. Just generate new seedphrase again.**

### Further Information

*This guide covers the basic steps to install and run an MSB validator node.*

*If you would like to learn more or explore additional configuration options, please refer to the official* [*repository*](https://github.com/Trac-Systems/main_settlement_bus)*.*


# Hypermall

Validators install and use instances of the Main Settlement Bus (MSB). Learn how to install & operate the MSB for Hypermall on Windows, MacOS and Linux.

Note: The below are instructions to install validators (MSB) for the Hypermall, **not** the Hypermall itself!

First we need to install **Node.js**. If you are not tech-savvy, don't worry, it's straight-forward. If you already have Node installed, you can skip its installation and move straight to the Pear installation.

### **Windows**

Open a PowerShell (right click > run as admin) and enter:

```powershell
# allow script execution
# if you are an admin, you may use more restricted settings based on your dev op rules
Set-ExecutionPolicy Unrestricted

# Download and install fnm:
winget install Schniz.fnm

# set some environment options
fnm env --use-on-cd | Out-String | Invoke-Expression

# Download and install Node.js:
fnm install 22

# Verify the Node.js version:
node -v # Should print a version like "v22.14.0".

# Verify npm version:
npm -v # Should print a version like "10.9.2".
```

### **MacOS/Linux**

Open a terminal/console and enter:

```bash
# Download and install nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash

# in lieu of restarting the shell
\. "$HOME/.nvm/nvm.sh"

# Download and install Node.js:
nvm install 22

# Verify the Node.js version:
node -v # Should print a version like "v22.15.0".

nvm current # Should print a version like "v22.15.0".

# Verify npm version:
npm -v # Should print a version like "10.9.2".
```

Once you confirmed the commands "node -v" and "npm -v" return no error but print a version number,  we can proceed.\
Should curl for some reason not exist, then install it first. If it is missing on MacOS, you can try "/usr/bin/curl" instead of just "curl".

### **Installing Pear Runtime**

Next is installing the **Pear Runtime**. The MSB runs on top of it and allows for efficient MSB execution. In principle running the MSB using plain Node works, but it is recommended to use Pear.

Simply execute the following in your shell / console / terminal:

```bash
npm i -g pear
pear -v
```

If pear asks you to set the paths after installation, please execute the "export" command that is provided.

"pear -v" should print the current version that you installed and you're ready to continue installing the MSB. It is important that you use the exact version of the MSB as this is the final release that everyone running an MSB must be operating on:

**Windows**

In your file explorer, create an empty directory called "my-msb". Then use the PowerShell to enter it:

```powershell
cd my-msb
```

**Linux / MacOS**

Create an empty directory using the console / terminal and enter it:

```bash
# create
mkdir my-msb

# enter the new folder
cd my-msb
```

### **Install/Update the MSB**

You may now choose between decentralized MSB distribution or manual install.

Updates works like this:&#x20;

* Re-install the latest version (latest are always the versions below)
* Start with the exact store name(s) that you used upon install and first run (e.g. store1, store2, etc)
* Type "/add\_writer" and hit enter in the validator screen to make sure the network handles your update quicker
* Skip the onboarding and post-install process below entirely
* Note: If it asks you for a new seedphrase, you might have not choosen the correct store name upon restart

**Decentralized**

To run the MSB, execute the following command:

```bash
pear run pear://h17deo6fwaats9x5jupx8m337yypnbgssge46t8k1ysxz6fxqnuy store1
```

You will be asked to enter the word "TRUST" once. Please do so and once done, follow the instructions and simply repeat the above command. You can skip the below and continue from the "Post Install" section.&#x20;

**Manual**

Now install the MSB exactly like this (the version number is important):

```bash
npm install trac-msb@0.1.82
```

If the installation finished without errors, we can start the MSB itself.

### **Starting the MSB**

Copy the msb as pear doesn't like it to be run in node\_modules.

**Windows:**

Use your file explorer and copy all contents from the folder "my-msb/node\_modules/trac-msb/" into "my-msb" (override existing files).

**Linux/MacOS**

```bash
cp -fr node_modules/trac-msb/* .
```

Then proceed (Windows/Linux/MacOS):

```bash
# pull the dependencies
npm install

# run the msb using pear
pear run . store1
```

**Note: In rare cases, there is a chance nothing appears to happen. In this case, please kill all "pear-runtime" processes using the process manager of your operating system. Then start again. A bug report has been sent to the pear team.**

### **Post Install**

"pear run" starts the MSB in the current directory "." and "store1" is the name of the database (located in stores/store1). The database also contains a backup of your identity. Your identity is important to claim trading fees earned by validating the Hypermall.

**Note:** To run multiple instances, simply use a different store name than "store1". You will need to run multiple instances if you own more than one license, because each validator has its associated identity.

After the MSB started the first time without identity, you should see the following information:

<figure><img src="/files/7YEoR1f30kCd6psgxpwt" alt=""><figcaption></figcaption></figure>

Please type "1" and hit enter and show you the seed phrase. Please copy and backup the seed phrase in a safe location:

<figure><img src="/files/OSixWPHFiwDkXHGBdCvn" alt=""><figcaption></figcaption></figure>

If you forgot to back it up, you can still make a backup of your identity like so:

**Windows**

Use your file explorer and copy the file "my-msb/stores/store1/db/keypair.json" to a backup folder of your choice.

**Linux / MacOS**

```bash
cp stores/store1/db/keypair.json /some/backup/location/
```

The keypair.json file contains the seed phrase that you can use to restore your identity in a new installation or logging into the Hypermall to withdraw your rewards.

The MSB has been fully started if you see the following:

<figure><img src="/files/vt1AlZxTX7gzGzuWfjAp" alt=""><figcaption></figcaption></figure>

To stop your MSB, enter "/exit" (use the CTRL + C keys on your keyboard only as last resort).&#x20;

To restart use the exact same start parameters you used earlier from within your MSB installation folder.

### **Getting Whitelisted**

We are assuming that your MSB instance (your address) isn't whitelisted yet. In order to validate your license, keep your Bitcoin wallet at hand and navigate to the [public onboarding site](https://onboarding.tracvalidator.com/).&#x20;

Follow the instructions to swap your licenses on Bitcoin to your Trac Network identities (the MSB Addresses).

Once received, you will be added to the whitelist after some processing time (up to 24 hours). Once added, please  restart your MSB, type  the command "/add\_writer" and hit enter. After a few seconds you should see a confirmation about your MSB being added.&#x20;

From that moment on, every trade transaction that your MSB validates will add to your fee balance in the Hypermall (given your MSB is running, a continuous uptime is important).

As mentioned above, for multiple licenses, please repeat the process of starting the MSB with different stores as each validator is supposed to have its own identity.

<mark style="color:red;">**Important:**</mark>**&#x20;Never run the same store twice as you would try to join the network 2 or more times with the same identity and cause "chatter" (permanent logins and logouts). To avoid reconnecting issues, make sure to run each store only once.**

### **Running the MSB in Background**

There are several ways to run the MSB in the background. For portability and cross-operating-system support, we recommend to use the Node package "pm2". [See here](https://pm2.io/docs/runtime/guide/installation/) on how to install (using the "npm" method is fine).

Once installed, start the MSB like this:

```bash
pm2 start pear --name "My MSB #1" --no-autorestart -- run . store1
```

<mark style="color:red;">**Important**</mark>**: If you use PM2, please start your instances using the "--no-autorestart" flag, otherwise you may run into issues.**

Check the pm2 documentation for further details as it is an extremely powerful tool to manage multiple instances of programs that are supposed to run in the background.

### **Congratulations!**

You just learned how to install and use an MSB and become a validator! As soon as the Hypermall is being released, you can log into the Hypermall with each of your validator identities and collect your earned trading fees by checking your balances.


# Developers

Trac Network is currently available as Release 1 that can be used to create smart contracts in a peer-to-peer (decentralized) fashion, but detached from the upcoming mainnet release.

The concept of Trac Network allows to create independent subnets. The use of subnets are encouraged if the different use-cases don't require the full validation power of mainnet. This means that using **Release 1** won't return validator rewards in $trac but in turn operate gas-less. Incentives to validate can then be provided from within the different apps that are being developed.

If a developer requires help from existing validators, developers can ask approved validators to secure their subnet. Validators  are pre-approved through licenses and committed to support the entire Trac Network, including subnets.

Due to high TPS and fast transaction finality. **Release 1** is especially equipped to support slower crypto networks.&#x20;

**Note:** Developing and releasing apps with **Release 1** will  continue to work post-mainnet release as these are independent subnets.

This gives developers the opportunity to develop several kinds of solutions without waiting for mainnet release. The there are a lot of use-cases that can already be covered but are not limited to:

* **Interoperability Layers**
  * Cross-chain communication
  * Cross-chain marketplaces, staking, lending (DeFi)
  * Real World Asset (RWA) trading
  * Bridges
  * Co-Processing
  * L2-apps
  * Oracles
* **L1s & L2s & Rollups**
  * Create entire crypto infrastructures just on a single Trac Network subnet
* **Chats / Social Networks**
  * Extend the built-in chat features using smart contracts
  * AI agents trigger contract interactions (trading e.g.)
  * Decentralized social networks
* **Content**
  * Buy/sell content (text, sound, video, games)
  * Content rendering (games, websites, books, comics)
  * Content creation (decentralized workgroups)
* **Gaming**
  * Multiplayer games
  * Metaverse experiences
  * In-game/metaverse rewards & marketplaces
* and many more!

What you shouldn't use **Release 1** for is to create token/collectible standards as there is no inter-contract communication implemented yet. This feature will be available in subsequent releases and mainnet.


# Mainnet

dApp (Tuxemon)


# Wallet API

The API is designed for dApps to:

* Connect to the user’s wallet
* Read identity + balance
* Sign messages
* Construct/sign/push Trac Network (TNK) transfers
* Sign Trac **contract** transactions (for `trac-peer`)

This page is based on the reference implementation in `tap-wallet-extension` (main branch) in this workspace.

### Conventions

* All methods are async and return Promises.
* Hex strings are lowercase/uppercase tolerant unless stated otherwise.
* Addresses are bech32m strings with `trac1...` prefix.
* Amounts/balances are returned as strings (typically smallest-unit / integer strings).

### Methods

#### `tracnetwork.requestAccount()`

Connects the wallet and returns the connected address.

* Params: none
* Returns: `Promise<string>` (wallet address)

```js
const address = await window.tracnetwork.requestAccount();
```

Example response:

```txt
trac1wnky35sgxefesuja46yvyf3tmf7pneeqv3ns5pk5pjlzu082f23s4r7923
```

#### `tracnetwork.getAddress()`

Returns the currently connected address.

* Params: none
* Returns: `Promise<string>` (wallet address)

```js
const address = await window.tracnetwork.getAddress();
```

#### `tracnetwork.getBalance()`

Returns the TNK balance of the connected account.

* Params: none
* Returns: `Promise<string>` (balance)

```js
const balance = await window.tracnetwork.getBalance();
```

Example response:

```txt
938000000000000000
```

#### `tracnetwork.getNetwork()`

Returns the current wallet network.

* Params: none
* Returns: `Promise<string>` (typically `"livenet"` or `"testnet"`)

```js
const network = await window.tracnetwork.getNetwork();
```

#### `tracnetwork.switchNetwork(network)`

Switches wallet network.

* Params:
  * `network: string` (accepted values include `"livenet"`, `"mainnet"`, `"testnet"`)
* Returns: `Promise<string>` (network name, e.g. `"testnet"`)

```js
await window.tracnetwork.switchNetwork("testnet");
```

Example response:

```txt
testnet
```

#### `tracnetwork.getPublicKey()`

Returns the connected wallet public key (hex).

* Params: none
* Returns: `Promise<string>` (public key hex, 32 bytes / 64 hex chars)

```js
const pubKey = await window.tracnetwork.getPublicKey();
```

Example response:

```txt
74ec48d208365398725dae88c2262bda7c19e72064670a06d40cbe2e3cea4aa3
```

#### `tracnetwork.signMessage(message)`

Signs an arbitrary message.

* Params:
  * `message: string`
* Returns: `Promise<{ signature: string, publicKey: string, address: string }>`

```js
const sig = await window.tracnetwork.signMessage("hello");
```

Example response shape:

```json
{
  "signature": "<hex-signature>",
  "publicKey": "<hex-publicKey>",
  "address": "trac1..."
}
```

Notes:

* Signing semantics are wallet-implementation-defined (what bytes are signed).
* When using this for a protocol that verifies signatures, ensure the verifier uses the same message bytes the wallet signed.

#### `tracnetwork.sendTNK(from, to, amount)`

Transfers TNK (high-level helper).

* Params:
  * `from: string`
  * `to: string`
  * `amount: string | number`
* Returns: `Promise<{ txHash: string, success: boolean }>`

```js
const res = await window.tracnetwork.sendTNK(fromAddress, toAddress, "1");
```

Example response:

```json
{
  "txHash": "6f7d901d63405873f3e845858b213603762bc4a1846a3f197a5a85092f84dd73",
  "success": true
}
```

#### `tracnetwork.buildTracTx({ from?, to, amount })`

Builds **and signs** a Trac Network TNK transfer and returns a broadcastable payload (base64 string).

* Params:
  * `{ from?: string, to: string, amount: string | number }`
* Returns: `Promise<string>` (`txPayload`, base64)

```js
const txPayload = await window.tracnetwork.buildTracTx({ to: recipient, amount: "1" });
```

#### `tracnetwork.pushTracTx(txPayload)`

Pushes a signed Trac Network transfer transaction payload (from `buildTracTx`) to the network.

* Params:
  * `txPayload: string` (base64)
* Returns: `Promise<{ txHash: string, success: boolean }>`

```js
const result = await window.tracnetwork.pushTracTx(txPayload);
```

Example response:

```json
{
  "txHash": "c3ce135ccc8e5d27b1b9ed153b05fada9937c5bf07085c4dbde601cb763f33e4",
  "success": true
}
```

#### `tracnetwork.signTracTx(contractTx)`

Signs a **contract transaction** (used with `trac-peer` contract RPC flows).

* Params:
  * `contractTx: { prepared_command: object, nonce: string, context: object }`
* Returns: `Promise<{ tx: string, signature: string }>`

Minimal example (shape):

```json
{
  "prepared_command": { "type": "catch", "value": {} },
  "nonce": "<hex32>",
  "context": {
    "networkId": 918,
    "txv": "<hex32>",
    "iw": "<hex32>",
    "bs": "<hex32 | optional>",
    "mbs": "<hex32>"
  }
}
```

Return value:

```json
{
  "tx": "<hex32>",
  "signature": "<hex64>"
}
```


# dApp Developer Guide

These docs are intended to be the “from zero to productive” guide for:

* running a peer against an MSB network
* creating and operating subnets (“apps”)
* connecting wallets and dApps over HTTP RPC
* building your own Protocol + Contract application on top of the peer runtime

If you previously felt “I can start it, but I don’t know what to do next”, start here.


# Introduction

### What are MSB and `trac-peer`?

#### MSB (Main Settlement Bus)

**MSB is the base network** that provides:

* transaction ordering / global log
* fee + anti-spam rules
* “finality” (confirmed/signed state)

If you want anything to be considered “settled on Trac”, it must exist in MSB confirmed state.

#### `trac-peer`

`trac-peer` is the **application layer**.

Each `trac-peer` instance runs (and participates in) a **subnet**: a smaller P2P network that:

* replicates its own ordered log
* executes a deterministic Contract locally on each node
* exposes RPC so wallets/dApps can interact with that Contract

In short: **MSB settles**, and **trac-peers run the apps/contracts**.

***

### What is a subnet (in one paragraph)

`trac-peer` runs a **subnet**: a smaller peer-to-peer network that maintains an ordered log and derives a deterministic application state from it (a contract/state machine).

For economic finality and anti-spam rules, transactions are **settled on MSB** (Main Settlement Bus). Subnet nodes only execute contract operations locally once they can prove the referenced transaction exists in MSB confirmed state.

***

### Who are you? (choose your path)

#### “I just want to run it locally”

Start with:

* [Quickstart](/documentation/developers/mainnet/dapp-developer-guide/quickstart)
* [Bootstrap checklist](/documentation/developers/mainnet/dapp-developer-guide/bootstrap-checklist) (canonical step-by-step)

#### “I want to run an RPC endpoint for wallets/dApps”

Start with:

* [RPC](broken://pages/PX3IMNof4JZz5LvB71MB)
* [Production notes](/documentation/developers/mainnet/dapp-developer-guide/production-notes) (ops/security notes)

#### “I want to build an app/contract on trac-peer”

Start with:

* [App dev](/documentation/developers/mainnet/dapp-developer-guide/app-dev)
* [Intercom example](/documentation/developers/mainnet/dapp-developer-guide/references-examples/intercom) (real project example)

***

### Core concepts (minimal)

* **MSB (Main Settlement Bus)**: the settlement layer. Transactions become “real” when MSB confirms them.
* **Subnet**: the P2P application layer. A subnet has:
  * a discovery **channel** (`--subnet-channel`)
  * a bootstrap/join-code **bootstrap** (`--subnet-bootstrap`, 32-byte hex)
* **Protocol**: defines how user commands map into typed ops: `{ type, value }`.
* **Contract**: deterministic state machine that executes those typed ops and writes state under `app/...`.
* **Operator vs client**:
  * operator uses CLI (admin/writer/indexer/chat/deploy)
  * client (wallet/dApp) uses RPC (schema/context/state/tx submit when enabled)

***

### One “golden” mental model

When a user “calls a contract function” they are not invoking code remotely.

Instead:

1. a client prepares a typed command `{ type, value }`
2. the wallet signs the transaction hash (MSB operation `type = 12`)
3. the peer broadcasts it to MSB (fees apply)
4. once MSB confirms it, the subnet appends a reference op
5. every subnet node executes the same contract logic locally and derives the same state

***


# Quickstart

## Local Dev

Goal: connect `trac-peer` to an already-running MSB network, create/join a subnet, and execute one demo contract operation.

### Prereqs

* Node.js + npm
* MSB network parameters:
  * `MSB_BOOTSTRAP`: 32-byte hex (64 hex chars)
  * `MSB_CHANNEL`: string
* An MSB “admin/funded” node that can transfer TNK to new addresses (so new peers can pay MSB fees).

### 1) Install

From the `trac-peer` repo folder:

```sh
npm install
```

### 2) Start the first peer (creates a new subnet)

This starts an in-process MSB *client node* (joins your MSB network) + the subnet peer node.

```sh
npm run peer:run -- \
  --msb-bootstrap=<MSB_BOOTSTRAP_HEX32> \
  --msb-channel=<MSB_CHANNEL> \
  --msb-store-name=peer-msb-1 \
  --peer-store-name=peer1 \
  --subnet-channel=tuxedex-v1
```

On first run, the peer will generate a **subnet bootstrap** and persist it to:

* `stores/peer1/subnet-bootstrap.hex`

Share that hex with joiners.

### 3) Fund the peer on MSB

In the peer logs you’ll see:

* `Peer MSB address: trac1...`

On your MSB funded/admin node, transfer TNK to that address (any amount that covers fees for a few txs).

Why: MSB rejects txs from addresses that don’t exist in MSB state or can’t pay fees.

### 4) Deploy the subnet (one-time per subnet)

In the peer terminal:

```txt
/deploy_subnet
```

This broadcasts an MSB operation that registers the subnet bootstrap + subnet channel.

### 5) Execute a demo contract operation

The default demo app is “Tuxemon”. It supports a single tx type:

* `catch`

In the peer terminal:

```txt
/tx --command "catch"
```

Then query your local subnet state:

```txt
/get --key app/tuxedex/<your-wallet-publicKey-hex> --confirmed false
```

You can print your pubkey via:

```txt
/stats
```

### 6) Start a second peer (join the existing subnet)

Use the subnet bootstrap from `stores/peer1/subnet-bootstrap.hex`.

```sh
npm run peer:run -- \
  --msb-bootstrap=<MSB_BOOTSTRAP_HEX32> \
  --msb-channel=<MSB_CHANNEL> \
  --msb-store-name=peer-msb-2 \
  --peer-store-name=peer2 \
  --subnet-channel=tuxedex-v1 \
  --subnet-bootstrap=<SUBNET_BOOTSTRAP_HEX32>
```

Fund `peer2`’s printed `Peer MSB address` the same way.

### Notes

* If you put flags on a new line without `\`, `zsh` will treat them as a new shell command.
* `confirmed=false` reads the local (unconfirmed) subnet view. `confirmed=true` reads the subnet’s signed view. These are subnet-specific, not “MSB finality”.


# MSB Local setup

This page explains how to run and operate an **MSB (Main Settlement Bus)** node locally so you can:

* fund accounts (peer MSB addresses + wallet MSB addresses)
* deploy/check subnet registrations
* inspect txs and debug settlement

It does **not** modify the MSB repo; it only documents how to use it.

***

### 0) Glossary (quick)

* **bootstrap**: 32-byte hex id (64 hex chars) identifying the MSB Autobase.
* **channel**: discovery topic (string) that MSB nodes must share to find each other.
* **store**: local persisted data for one node (usually `stores/<name>/...`).
* **MSB address**: bech32m address like `trac1...`.
* **fee**: MSB-level fee required to broadcast a transaction.
* **confirmed vs unconfirmed** (MSB): confirmed = signed state, unconfirmed = local unsigned view.

***

### 1) Prereqs

* Node.js + npm
* Pear (MSB scripts typically use Pear)

```sh
npm install -g pear
pear -v
```

Optional (if you run MSB tests):

```sh
npm install -g bare
```

***

### 2) Install MSB repo

From your workspace root:

```sh
git clone -b main --single-branch git@github.com:Trac-Systems/main_settlement_bus.git
cd main_settlement_bus
npm install
```

***

### 3) Run MSB locally

#### 3.1 Interactive CLI node (recommended)

```sh
MSB_STORE=node1 npm run env-prod
```

This starts an interactive MSB node and stores data under `main_settlement_bus/stores/node1/`.

#### 3.2 RPC node (optional)

```sh
MSB_STORE=rpc-node-store MSB_HOST=127.0.0.1 MSB_PORT=5000 npm run env-prod-rpc
```

***

### 4) Admin mode (if you need whitelisting/init)

MSB has extra admin-only commands (whitelisting, initialization migrations, banning). In the current MSB codebase, admin mode is inferred by store name:

```sh
MSB_STORE=admin npm run env-prod
```

***

### 5) MSB commands you’ll use most

Inside the MSB terminal, run `/help` for the full list. Common ones:

#### Network/state inspection

* `/stats`
* `/confirmed_length`
* `/unconfirmed_length`
* `/get_fee`
* `/get_txv`

#### Account inspection

* `/get_balance <address> <confirmed>` (confirmed defaults to `true`)
* `/node_status <address>`

#### Funding (critical for `trac-peer`)

To make an address “exist” in MSB state and have fee balance:

```txt
/transfer <to_address> <amount>
```

You must fund:

* each **peer’s** printed `Peer MSB address` (peer node operations)
* each **wallet user** MSB address (the requester) that will sign contract txs

If you don’t, you’ll see errors like:

* `Requester address not found in state`
* insufficient fee balance

#### Subnet deployment entries (for `trac-peer`)

* `/deployment <subnet_bootstrap_hex32> <channel>`
* `/get_deployment <subnet_bootstrap_hex32>`

#### Tx lookup / debugging

* `/get_tx_info <tx_hash>`
* `/get_tx_details <tx_hash>`
* `/get_extended_tx_details <tx_hash> <confirmed>`
* `/get_txs_hashes <start> <end>`

***

### 6) Whitelisting + initial balances (optional bootstrap workflow)

MSB supports an initialization phase that reads from files:

* `main_settlement_bus/migration/initial_balances.csv`
* `main_settlement_bus/whitelist/addresses.csv`

In an MSB admin terminal you can run:

* `/balance_migration`
* `/add_whitelist`
* `/disable_initialization`

***

### 7) Next: run `trac-peer`

Once MSB is running and you know:

* `MSB_BOOTSTRAP`
* `MSB_CHANNEL`

continue with:

* &#x20;[Bootstrap checklist](/documentation/developers/mainnet/dapp-developer-guide/bootstrap-checklist) (canonical A→Z checklist)


# Bootstrap checklist

## Bootstrap Checklist (Local Dev)

This is the single “canonical” checklist for getting a working local setup:

* MSB network running
* `trac-peer` connected to that MSB network
* subnet deployed and usable
* demo tx executed and state query verified

It’s written to be copy/paste friendly and to remove ambiguity about “which value goes where”.

***

### Values you need (fill these in)

| Name               | What it is                                         | Where you get it                                                               |
| ------------------ | -------------------------------------------------- | ------------------------------------------------------------------------------ |
| `MSB_BOOTSTRAP`    | 32-byte hex (64 chars) identifying the MSB network | From your MSB node logs/`/stats` (often shown as `msb.writerKey`)              |
| `MSB_CHANNEL`      | discovery string for MSB                           | From your MSB node logs/config                                                 |
| `SUBNET_CHANNEL`   | discovery string for the subnet/app                | you choose (e.g. `my-app-v1`)                                                  |
| `SUBNET_BOOTSTRAP` | 32-byte hex join-code for the subnet               | generated by peer1 and persisted to `stores/<peer-store>/subnet-bootstrap.hex` |

Recommended defaults:

* `SUBNET_CHANNEL=my-app-v1`

***

### 0) Install `trac-peer`

```sh
cd trac-peer
npm install
```

***

### 1) MSB: confirm the network parameters

On your already-running MSB node:

1. Confirm MSB is running and note:
   * `MSB_BOOTSTRAP`
   * `MSB_CHANNEL`
2. Confirm you can fund a new address (you’ll do this for each peer) using MSB CLI (example):

```txt
/transfer <to-address> <amount>
```

If you don’t know how to run MSB locally, start with:

* `MSB_LOCAL_SETUP.md`

***

### 2) Start peer1 (creates the subnet)

Pick store names (so you can run multiple peers on one machine):

* `--peer-store-name=peer1`
* `--msb-store-name=peer1-msb`

Run:

```sh
npm run peer:run -- \
  --msb-bootstrap="$MSB_BOOTSTRAP" \
  --msb-channel="$MSB_CHANNEL" \
  --peer-store-name=peer1 \
  --msb-store-name=peer1-msb \
  --subnet-channel="$SUBNET_CHANNEL"
```

Peer1 prints:

* `Peer MSB address: trac1...` ← fund this on MSB
* `Peer pubkey (hex): ...` ← used for subnet admin

Peer1 also creates (and persists) the subnet bootstrap join-code:

* `stores/peer1/subnet-bootstrap.hex`

Read it with:

```sh
cat stores/peer1/subnet-bootstrap.hex
```

***

### 3) Fund peer1 on MSB (required for txs)

On your MSB funded/admin node, transfer TNK to peer1’s MSB address.

If peer1 is not funded, tx submission will fail with errors like:

* `Requester address not found in state`
* insufficient fee balance

***

### 4) Deploy the subnet (one-time per subnet)

In the peer1 terminal:

```txt
/deploy_subnet
```

This registers the subnet in MSB (so settlement and MSB preflight checks work).

***

### 5) Become subnet admin (recommended)

In peer1:

```txt
/add_admin --address <peer1-publicKey-hex>
```

Verify:

```txt
/get --key admin --confirmed false
```

***

### 6) Execute a demo contract tx (Tuxemon)

The demo app supports:

* `catch`

In peer1:

```txt
/tx --command "catch"
```

Read your tuxedex (unconfirmed is fine for dev):

```txt
/get --key app/tuxedex/<peer1-publicKey-hex> --confirmed false
```

***

### 7) Start peer2 (join the subnet)

Get `SUBNET_BOOTSTRAP` from `stores/peer1/subnet-bootstrap.hex` and run:

```sh
npm run peer:run -- \
  --msb-bootstrap="$MSB_BOOTSTRAP" \
  --msb-channel="$MSB_CHANNEL" \
  --peer-store-name=peer2 \
  --msb-store-name=peer2-msb \
  --subnet-channel="$SUBNET_CHANNEL" \
  --subnet-bootstrap="$SUBNET_BOOTSTRAP"
```

Fund peer2’s printed `Peer MSB address` on MSB as well.

***

### 8) Writers/indexers (optional for multi-writer subnets)

If your subnet is gated (default), peer2 may replicate reads but won’t be allowed to append writes unless admin approves it.

On peer2, get its writer key:

```txt
/stats
```

On peer1 (admin), add peer2:

```txt
/add_writer --key <peer2-writerKey-hex>
```

Optionally also add it as an indexer:

```txt
/add_indexer --key <peer2-writerKey-hex>
```

If you want an open subnet instead:

```txt
/set_auto_add_writers --enabled 1
```

***

### 9) Enable RPC (wallet/dApp connectivity)

Start a peer with HTTP RPC:

```sh
npm run peer:run -- \
  --msb-bootstrap="$MSB_BOOTSTRAP" \
  --msb-channel="$MSB_CHANNEL" \
  --peer-store-name=peer-rpc \
  --msb-store-name=peer-rpc-msb \
  --subnet-channel="$SUBNET_CHANNEL" \
  --subnet-bootstrap="$SUBNET_BOOTSTRAP" \
  --rpc \
  --rpc-host 127.0.0.1 \
  --rpc-port 5001
```

If you want wallets/dApps to be able to submit txs over HTTP, add:

```txt
--api-tx-exposed
```

Quick check:

```sh
curl -s http://127.0.0.1:5001/v1/health | jq
curl -s http://127.0.0.1:5001/v1/contract/schema | jq
curl -s http://127.0.0.1:5001/v1/contract/tx/context | jq
```


# Subnets and roles

## Subnets, Deployment, and Roles

This page explains how subnet registration works (MSB-side) and how admin/writer/indexer roles work (subnet-side).

### 1) Deploying a subnet (MSB registration)

Before contract txs can settle cleanly, the subnet must be registered in MSB.

On a running peer (typically the first node of the subnet), run:

```txt
/deploy_subnet
```

This broadcasts an MSB operation that registers:

* subnet bootstrap (`bs`)
* subnet channel (`ic` as a 32-byte identifier)

Peers can still replicate P2P without deploying, but MSB preflight checks and indexing behavior expect the subnet to exist in MSB state.

### 2) Admin role (subnet governance)

Subnet admin is stored inside the subnet state under:

* `admin`

On the bootstrap node, set the admin once:

```txt
/add_admin --address <peer-publicKey-hex>
```

Transfer admin later:

```txt
/update_admin --address <peer-publicKey-hex>
```

Verify:

```txt
/get --key admin --confirmed false
```

Notes:

* `--address` here is the **peer public key hex**, not a bech32 MSB address.

### 3) Writers vs indexers (Autobase roles)

Subnets use Autobase roles:

* **writers** can append subnet operations
* **indexers** participate in the linearization/indexing process (what advances the subnet’s signed/confirmed view)

#### Recommended indexer counts (rule of thumb)

How many peers you run depends on your application’s risk profile.

Rule of thumb:

* **Non-financial apps**: run **1 indexer**
* **Financial / “value” apps**: run **3 indexers**

For 3-indexer deployments, run them:

* in different datacenters/regions
* under different authorities/teams (if possible)

#### The first indexer is usually the admin

Operationally, the first node you bring up for a new subnet is typically:

* the subnet **admin** (governance)
* an **indexer** (so the subnet can actually produce confirmed state)

Admin commands:

```txt
/add_writer --key <writerKeyHex>
/remove_writer --key <writerKeyHex>

/add_indexer --key <writerKeyHex>
/remove_indexer --key <writerKeyHex>
```

Get your local writer key from:

```txt
/stats
```

### 4) Auto-add writers

Admins can allow nodes to become writers automatically when they join:

```txt
/set_auto_add_writers --enabled 1
```

This is subnet-scoped and only affects Autobase writer admission.

### 5) Transactions enabled switch

Subnets have a transactions gate:

* `txen` (transactions enabled)

Enable:

```txt
/enable_transactions
```

If disabled, subnet tx indexing and/or RPC tx submission will be blocked.

### 6) Chat system

The built-in chat system is off by default:

```txt
/set_chat_status --enabled 1
```

Post a message:

```txt
/post --message "hello"
```

Chat is a subnet feature. It does not move TNK; it only writes subnet state.

### 7) Confirmed vs unconfirmed subnet reads

When you query subnet state:

* `confirmed=false` reads the current local view (unsigned)
* `confirmed=true` reads the subnet’s signed view

These are properties of the subnet log/indexing, not “MSB settlement”.


# Running trac-peer

## Running `trac-peer` Nodes

This page documents the node runners, CLI flags/env, and how “bootstrapping vs joining” works.

### Two layers run together

`trac-peer` currently runs *on top of an in-process MSB client node* (from `trac-msb`).

That MSB client node is required to:

* broadcast subnet deployments + contract tx payloads to MSB, and
* observe MSB “confirmed” state to decide which MSB txs can be executed locally in the subnet.

You still run your own MSB network separately; `trac-peer` just joins it via this embedded client node.

### Runners

#### Node runner (recommended to start)

```sh
npm run peer:run -- --msb-bootstrap=<hex32> --msb-channel=<string>
```

#### Pear runner

```sh
npm run peer:pear -- --msb-bootstrap=<hex32> --msb-channel=<string>
```

Pear is useful when you want parity with Pear-based MSB runs. If Pear warns about PATH, follow its message once so `pear` resolves directly.

### Required MSB params

You must provide:

* `--msb-bootstrap=<hex32>` (64 hex chars)
* `--msb-channel=<string>`

Or env vars:

* `MSB_BOOTSTRAP`
* `MSB_CHANNEL`

### Stores (running multiple nodes on one machine)

Each node has:

* a **peer store** (subnet state/logs)
* an **msb store** (embedded MSB client state/logs)

Use distinct names when running multiple nodes locally:

```sh
npm run peer:run -- \
  --msb-bootstrap=<hex32> \
  --msb-channel=<string> \
  --msb-store-name=peer-msb-1 \
  --peer-store-name=peer1
```

Defaults:

* `--peer-stores-directory` defaults to `stores/`
* `--msb-stores-directory` defaults to `stores/`
* `--msb-store-name` defaults to `<peer-store-name>-msb`

Keypairs are stored at:

* `stores/<msb-store>/db/keypair.json`
* `stores/<peer-store>/db/keypair.json`

### Subnet identity: channel + bootstrap

All nodes in the same subnet must share:

* `--subnet-channel=<string>`
* `--subnet-bootstrap=<hex32>`

#### Creating a new subnet (bootstrap node)

If you omit `--subnet-bootstrap`, `trac-peer` will generate one and persist it to:

* `stores/<peer-store-name>/subnet-bootstrap.hex`

That file is the join-code for other peers.

#### Joining an existing subnet (joiners)

Pass the bootstrap hex and the same channel:

```sh
npm run peer:run -- \
  --msb-bootstrap=<hex32> \
  --msb-channel=<string> \
  --msb-store-name=peer-msb-2 \
  --peer-store-name=peer2 \
  --subnet-channel=tuxedex-v1 \
  --subnet-bootstrap=<SUBNET_BOOTSTRAP_HEX32>
```

### RPC mode vs interactive CLI

`scripts/run-peer.mjs` disables the interactive terminal when RPC is enabled.

Enable RPC:

```sh
npm run peer:run -- \
  --msb-bootstrap=<hex32> \
  --msb-channel=<string> \
  --rpc \
  --rpc-host=127.0.0.1 \
  --rpc-port=5001
```

If you also want dApps/wallets to submit txs over HTTP, add:

```txt
--api-tx-exposed
```

If you start without `--rpc`, `--api-tx-exposed` is ignored (operator safety).

Env equivalents:

* `PEER_RPC=1`
* `PEER_RPC_HOST=127.0.0.1`
* `PEER_RPC_PORT=5001`
* `PEER_API_TX_EXPOSED=1`

### Flag format (important for shells)

Both are accepted:

* `--subnet-bootstrap=<hex32>`
* `--subnet-bootstrap <hex32>`

When using `npm run ...`, remember the double-dash:

```sh
npm run peer:run -- --msb-bootstrap=<hex32> --msb-channel=<string>
```

Without the second `--`, npm will swallow flags.


# trac-peer RPC API v1

## Health check

> Basic liveness probe.<br>

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"tags":[{"name":"Health"}],"servers":[{"url":"http://127.0.0.1:5001","description":"Local dev example"}],"paths":{"/v1/health":{"get":{"tags":["Health"],"summary":"Health check","description":"Basic liveness probe.\n","operationId":"health","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}}},"components":{"schemas":{"HealthResponse":{"type":"object","additionalProperties":false,"properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}}
```

## Peer + MSB status summary

> Status summary for the running peer and its embedded MSB client view.\
> \
> Useful for UIs to display:\
> \- peer identity (pubKeyHex, writerKeyHex, msbAddress)\
> \- subnet info (bootstrap/channel, signed/unsigned lengths)\
> \- MSB view (bootstrapHex, networkId, signedLength)<br>

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"tags":[{"name":"Status"}],"servers":[{"url":"http://127.0.0.1:5001","description":"Local dev example"}],"paths":{"/v1/status":{"get":{"tags":["Status"],"summary":"Peer + MSB status summary","description":"Status summary for the running peer and its embedded MSB client view.\n\nUseful for UIs to display:\n- peer identity (pubKeyHex, writerKeyHex, msbAddress)\n- subnet info (bootstrap/channel, signed/unsigned lengths)\n- MSB view (bootstrapHex, networkId, signedLength)\n","operationId":"status","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusResponse"}}}},"500":{"$ref":"#/components/responses/ErrorResponse"}}}}},"components":{"schemas":{"StatusResponse":{"type":"object","additionalProperties":false,"properties":{"peer":{"$ref":"#/components/schemas/StatusPeer"},"msb":{"$ref":"#/components/schemas/StatusMsb"}},"required":["peer","msb"]},"StatusPeer":{"type":"object","additionalProperties":true,"properties":{"pubKeyHex":{"allOf":[{"$ref":"#/components/schemas/Hex32"}]},"writerKeyHex":{"allOf":[{"$ref":"#/components/schemas/Hex32"}]},"msbAddress":{"allOf":[{"$ref":"#/components/schemas/Bech32mTracAddress"}]},"baseWritable":{"type":"boolean"},"isIndexer":{"type":"boolean"},"isWriter":{"type":"boolean"},"subnetBootstrapHex":{"allOf":[{"$ref":"#/components/schemas/Hex32"}],"nullable":true},"subnetChannelUtf8":{"type":"string","nullable":true},"subnetSignedLength":{"type":"integer","format":"int64","nullable":true},"subnetUnsignedLength":{"type":"integer","format":"int64","nullable":true},"admin":{"allOf":[{"$ref":"#/components/schemas/Hex32"}],"nullable":true},"chatStatus":{"type":"string","enum":["on","off"],"nullable":true}}},"Hex32":{"type":"string","description":"32-byte hex string (64 hex chars)","pattern":"^[0-9a-fA-F]{64}$"},"Bech32mTracAddress":{"type":"string","description":"Trac bech32m address","pattern":"^trac1[0-9a-z]+$"},"StatusMsb":{"type":"object","additionalProperties":true,"properties":{"ready":{"type":"boolean"},"bootstrapHex":{"allOf":[{"$ref":"#/components/schemas/Hex32"}]},"networkId":{"type":"number"},"signedLength":{"type":"number"}},"required":["ready","bootstrapHex","networkId","signedLength"]},"ErrorResponse":{"type":"object","additionalProperties":false,"properties":{"error":{"type":"string"}},"required":["error"]}},"responses":{"ErrorResponse":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}
```

## Contract schema (ABI-like discovery)

> ABI-like discovery document for wallets/dApps.\
> \
> Includes:\
> \- supported contract transaction types (\`contract.txTypes\`)\
> \- per-op input schema hints (\`contract.ops\`)\
> \- protocol API method schema (\`api.methods\`)<br>

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"tags":[{"name":"Contract"}],"servers":[{"url":"http://127.0.0.1:5001","description":"Local dev example"}],"paths":{"/v1/contract/schema":{"get":{"tags":["Contract"],"summary":"Contract schema (ABI-like discovery)","description":"ABI-like discovery document for wallets/dApps.\n\nIncludes:\n- supported contract transaction types (`contract.txTypes`)\n- per-op input schema hints (`contract.ops`)\n- protocol API method schema (`api.methods`)\n","operationId":"contractSchema","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContractSchemaResponse"}}}},"500":{"$ref":"#/components/responses/ErrorResponse"}}}}},"components":{"schemas":{"ContractSchemaResponse":{"type":"object","additionalProperties":false,"properties":{"schemaVersion":{"type":"number"},"schemaFormat":{"type":"string","enum":["json-schema"]},"contract":{"$ref":"#/components/schemas/ContractSchemaContract"},"api":{"$ref":"#/components/schemas/ContractSchemaApi"}},"required":["schemaVersion","schemaFormat","contract","api"]},"ContractSchemaContract":{"type":"object","additionalProperties":true,"properties":{"contractClass":{"type":"string","nullable":true},"protocolClass":{"type":"string","nullable":true},"txTypes":{"type":"array","items":{"type":"string"}},"ops":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ContractSchemaOp"}}},"required":["contractClass","protocolClass","txTypes","ops"]},"ContractSchemaOp":{"type":"object","additionalProperties":true,"properties":{"key":{"$ref":"#/components/schemas/JsonSchema"},"value":{"$ref":"#/components/schemas/JsonSchema"}},"required":["value"]},"JsonSchema":{"type":"object","description":"JSON Schema object (contract op schema or API schema)","additionalProperties":true},"ContractSchemaApi":{"type":"object","additionalProperties":true,"properties":{"methods":{"type":"object","additionalProperties":true}},"required":["methods"]},"ErrorResponse":{"type":"object","additionalProperties":false,"properties":{"error":{"type":"string"}},"required":["error"]}},"responses":{"ErrorResponse":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}
```

## Generate a nonce for contract tx signing

> Generates a nonce used in the wallet signing payload.<br>

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"tags":[{"name":"Contract"}],"servers":[{"url":"http://127.0.0.1:5001","description":"Local dev example"}],"paths":{"/v1/contract/nonce":{"get":{"tags":["Contract"],"summary":"Generate a nonce for contract tx signing","description":"Generates a nonce used in the wallet signing payload.\n","operationId":"contractNonce","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NonceResponse"}}}},"500":{"$ref":"#/components/responses/ErrorResponse"}}}}},"components":{"schemas":{"NonceResponse":{"type":"object","additionalProperties":false,"properties":{"nonce":{"$ref":"#/components/schemas/Hex32"}},"required":["nonce"]},"Hex32":{"type":"string","description":"32-byte hex string (64 hex chars)","pattern":"^[0-9a-fA-F]{64}$"},"ErrorResponse":{"type":"object","additionalProperties":false,"properties":{"error":{"type":"string"}},"required":["error"]}},"responses":{"ErrorResponse":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}
```

## Get tx signing context (MSB context)

> Returns the MSB tx context required for wallets to compute the contract tx hash.\
> \
> The wallet typically signs a payload derived from:\
> \- \`{ prepared\_command, nonce, context }\`<br>

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"tags":[{"name":"Contract"}],"servers":[{"url":"http://127.0.0.1:5001","description":"Local dev example"}],"paths":{"/v1/contract/tx/context":{"get":{"tags":["Contract"],"summary":"Get tx signing context (MSB context)","description":"Returns the MSB tx context required for wallets to compute the contract tx hash.\n\nThe wallet typically signs a payload derived from:\n- `{ prepared_command, nonce, context }`\n","operationId":"contractTxContext","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TxContextResponse"}}}},"500":{"$ref":"#/components/responses/ErrorResponse"}}}}},"components":{"schemas":{"TxContextResponse":{"type":"object","additionalProperties":false,"properties":{"msb":{"$ref":"#/components/schemas/TxContextMsb"}},"required":["msb"]},"TxContextMsb":{"type":"object","additionalProperties":false,"properties":{"networkId":{"type":"number"},"txv":{"$ref":"#/components/schemas/Hex32"},"iw":{"$ref":"#/components/schemas/Hex32"},"bs":{"$ref":"#/components/schemas/Hex32"},"mbs":{"$ref":"#/components/schemas/Hex32"},"operationType":{"type":"number"}},"required":["networkId","txv","iw","bs","mbs","operationType"]},"Hex32":{"type":"string","description":"32-byte hex string (64 hex chars)","pattern":"^[0-9a-fA-F]{64}$"},"ErrorResponse":{"type":"object","additionalProperties":false,"properties":{"error":{"type":"string"}},"required":["error"]}},"responses":{"ErrorResponse":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}
```

## Simulate or broadcast a wallet-signed contract transaction

> Submits a wallet-signed contract transaction.\
> \
> Notes:\
> \- Tx submission is opt-in; operators must start the peer with \`--api-tx-exposed\`.\
> \- Use \`sim=true\` first (preflight) and only broadcast (\`sim=false\`) if simulation succeeds.<br>

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"tags":[{"name":"Contract"}],"servers":[{"url":"http://127.0.0.1:5001","description":"Local dev example"}],"paths":{"/v1/contract/tx":{"post":{"tags":["Contract"],"summary":"Simulate or broadcast a wallet-signed contract transaction","description":"Submits a wallet-signed contract transaction.\n\nNotes:\n- Tx submission is opt-in; operators must start the peer with `--api-tx-exposed`.\n- Use `sim=true` first (preflight) and only broadcast (`sim=false`) if simulation succeeds.\n","operationId":"contractTx","requestBody":{"required":true,"description":"Wallet-signed contract transaction payload.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContractTxRequest"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContractTxResponse"}}}},"400":{"$ref":"#/components/responses/ErrorResponse"},"413":{"$ref":"#/components/responses/ErrorResponse"},"500":{"$ref":"#/components/responses/ErrorResponse"}}}}},"components":{"schemas":{"ContractTxRequest":{"type":"object","additionalProperties":false,"properties":{"tx":{"$ref":"#/components/schemas/Hex32"},"prepared_command":{"$ref":"#/components/schemas/PreparedCommand"},"address":{"$ref":"#/components/schemas/Hex32"},"signature":{"$ref":"#/components/schemas/Hex64"},"nonce":{"$ref":"#/components/schemas/Hex32"},"sim":{"type":"boolean","default":false}},"required":["tx","prepared_command","address","signature","nonce"]},"Hex32":{"type":"string","description":"32-byte hex string (64 hex chars)","pattern":"^[0-9a-fA-F]{64}$"},"PreparedCommand":{"type":"object","additionalProperties":true,"properties":{"type":{"type":"string","minLength":1,"maxLength":256},"value":{}},"required":["type","value"]},"Hex64":{"type":"string","description":"64-byte hex string (128 hex chars)","pattern":"^[0-9a-fA-F]{128}$"},"ContractTxResponse":{"type":"object","additionalProperties":true,"properties":{"result":{}},"required":["result"]},"ErrorResponse":{"type":"object","additionalProperties":false,"properties":{"error":{"type":"string"}},"required":["error"]}},"responses":{"ErrorResponse":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}
```

## Read a single key from subnet state

> Reads one key from subnet state.\
> \
> \- \`confirmed=true\` reads from the subnet \*\*signed\*\* view (final snapshots).\
> \- \`confirmed=false\` reads from the latest local view (fast UI updates).<br>

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"tags":[{"name":"State"}],"servers":[{"url":"http://127.0.0.1:5001","description":"Local dev example"}],"paths":{"/v1/state":{"get":{"tags":["State"],"summary":"Read a single key from subnet state","description":"Reads one key from subnet state.\n\n- `confirmed=true` reads from the subnet **signed** view (final snapshots).\n- `confirmed=false` reads from the latest local view (fast UI updates).\n","operationId":"getState","parameters":[{"name":"key","in":"query","required":true,"description":"Exact Hyperbee key","schema":{"type":"string","minLength":1,"maxLength":4096}},{"name":"confirmed","in":"query","required":false,"description":"If true, reads from signed view; if false, reads from latest (unsigned) view","schema":{"type":"boolean","default":true}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StateResponse"}}}},"400":{"$ref":"#/components/responses/ErrorResponse"},"500":{"$ref":"#/components/responses/ErrorResponse"}}}}},"components":{"schemas":{"StateResponse":{"type":"object","additionalProperties":false,"properties":{"key":{"type":"string"},"confirmed":{"type":"boolean"},"value":{}},"required":["key","confirmed","value"]},"ErrorResponse":{"type":"object","additionalProperties":false,"properties":{"error":{"type":"string"}},"required":["error"]}},"responses":{"ErrorResponse":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}
```

## The Hex32 object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"Hex32":{"type":"string","description":"32-byte hex string (64 hex chars)","pattern":"^[0-9a-fA-F]{64}$"}}}}
```

## The Hex64 object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"Hex64":{"type":"string","description":"64-byte hex string (128 hex chars)","pattern":"^[0-9a-fA-F]{128}$"}}}}
```

## The Bech32mTracAddress object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"Bech32mTracAddress":{"type":"string","description":"Trac bech32m address","pattern":"^trac1[0-9a-z]+$"}}}}
```

## The ErrorResponse object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"ErrorResponse":{"type":"object","additionalProperties":false,"properties":{"error":{"type":"string"}},"required":["error"]}}}}
```

## The StatusPeer object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"StatusPeer":{"type":"object","additionalProperties":true,"properties":{"pubKeyHex":{"allOf":[{"$ref":"#/components/schemas/Hex32"}]},"writerKeyHex":{"allOf":[{"$ref":"#/components/schemas/Hex32"}]},"msbAddress":{"allOf":[{"$ref":"#/components/schemas/Bech32mTracAddress"}]},"baseWritable":{"type":"boolean"},"isIndexer":{"type":"boolean"},"isWriter":{"type":"boolean"},"subnetBootstrapHex":{"allOf":[{"$ref":"#/components/schemas/Hex32"}],"nullable":true},"subnetChannelUtf8":{"type":"string","nullable":true},"subnetSignedLength":{"type":"integer","format":"int64","nullable":true},"subnetUnsignedLength":{"type":"integer","format":"int64","nullable":true},"admin":{"allOf":[{"$ref":"#/components/schemas/Hex32"}],"nullable":true},"chatStatus":{"type":"string","enum":["on","off"],"nullable":true}}},"Hex32":{"type":"string","description":"32-byte hex string (64 hex chars)","pattern":"^[0-9a-fA-F]{64}$"},"Bech32mTracAddress":{"type":"string","description":"Trac bech32m address","pattern":"^trac1[0-9a-z]+$"}}}}
```

## The HealthResponse object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"HealthResponse":{"type":"object","additionalProperties":false,"properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}}
```

## The StatusMsb object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"StatusMsb":{"type":"object","additionalProperties":true,"properties":{"ready":{"type":"boolean"},"bootstrapHex":{"allOf":[{"$ref":"#/components/schemas/Hex32"}]},"networkId":{"type":"number"},"signedLength":{"type":"number"}},"required":["ready","bootstrapHex","networkId","signedLength"]},"Hex32":{"type":"string","description":"32-byte hex string (64 hex chars)","pattern":"^[0-9a-fA-F]{64}$"}}}}
```

## The StatusResponse object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"StatusResponse":{"type":"object","additionalProperties":false,"properties":{"peer":{"$ref":"#/components/schemas/StatusPeer"},"msb":{"$ref":"#/components/schemas/StatusMsb"}},"required":["peer","msb"]},"StatusPeer":{"type":"object","additionalProperties":true,"properties":{"pubKeyHex":{"allOf":[{"$ref":"#/components/schemas/Hex32"}]},"writerKeyHex":{"allOf":[{"$ref":"#/components/schemas/Hex32"}]},"msbAddress":{"allOf":[{"$ref":"#/components/schemas/Bech32mTracAddress"}]},"baseWritable":{"type":"boolean"},"isIndexer":{"type":"boolean"},"isWriter":{"type":"boolean"},"subnetBootstrapHex":{"allOf":[{"$ref":"#/components/schemas/Hex32"}],"nullable":true},"subnetChannelUtf8":{"type":"string","nullable":true},"subnetSignedLength":{"type":"integer","format":"int64","nullable":true},"subnetUnsignedLength":{"type":"integer","format":"int64","nullable":true},"admin":{"allOf":[{"$ref":"#/components/schemas/Hex32"}],"nullable":true},"chatStatus":{"type":"string","enum":["on","off"],"nullable":true}}},"Hex32":{"type":"string","description":"32-byte hex string (64 hex chars)","pattern":"^[0-9a-fA-F]{64}$"},"Bech32mTracAddress":{"type":"string","description":"Trac bech32m address","pattern":"^trac1[0-9a-z]+$"},"StatusMsb":{"type":"object","additionalProperties":true,"properties":{"ready":{"type":"boolean"},"bootstrapHex":{"allOf":[{"$ref":"#/components/schemas/Hex32"}]},"networkId":{"type":"number"},"signedLength":{"type":"number"}},"required":["ready","bootstrapHex","networkId","signedLength"]}}}}
```

## The JsonSchema object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"JsonSchema":{"type":"object","description":"JSON Schema object (contract op schema or API schema)","additionalProperties":true}}}}
```

## The ContractSchemaOp object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"ContractSchemaOp":{"type":"object","additionalProperties":true,"properties":{"key":{"$ref":"#/components/schemas/JsonSchema"},"value":{"$ref":"#/components/schemas/JsonSchema"}},"required":["value"]},"JsonSchema":{"type":"object","description":"JSON Schema object (contract op schema or API schema)","additionalProperties":true}}}}
```

## The ContractSchemaContract object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"ContractSchemaContract":{"type":"object","additionalProperties":true,"properties":{"contractClass":{"type":"string","nullable":true},"protocolClass":{"type":"string","nullable":true},"txTypes":{"type":"array","items":{"type":"string"}},"ops":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ContractSchemaOp"}}},"required":["contractClass","protocolClass","txTypes","ops"]},"ContractSchemaOp":{"type":"object","additionalProperties":true,"properties":{"key":{"$ref":"#/components/schemas/JsonSchema"},"value":{"$ref":"#/components/schemas/JsonSchema"}},"required":["value"]},"JsonSchema":{"type":"object","description":"JSON Schema object (contract op schema or API schema)","additionalProperties":true}}}}
```

## The ContractSchemaApi object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"ContractSchemaApi":{"type":"object","additionalProperties":true,"properties":{"methods":{"type":"object","additionalProperties":true}},"required":["methods"]}}}}
```

## The ContractSchemaResponse object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"ContractSchemaResponse":{"type":"object","additionalProperties":false,"properties":{"schemaVersion":{"type":"number"},"schemaFormat":{"type":"string","enum":["json-schema"]},"contract":{"$ref":"#/components/schemas/ContractSchemaContract"},"api":{"$ref":"#/components/schemas/ContractSchemaApi"}},"required":["schemaVersion","schemaFormat","contract","api"]},"ContractSchemaContract":{"type":"object","additionalProperties":true,"properties":{"contractClass":{"type":"string","nullable":true},"protocolClass":{"type":"string","nullable":true},"txTypes":{"type":"array","items":{"type":"string"}},"ops":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ContractSchemaOp"}}},"required":["contractClass","protocolClass","txTypes","ops"]},"ContractSchemaOp":{"type":"object","additionalProperties":true,"properties":{"key":{"$ref":"#/components/schemas/JsonSchema"},"value":{"$ref":"#/components/schemas/JsonSchema"}},"required":["value"]},"JsonSchema":{"type":"object","description":"JSON Schema object (contract op schema or API schema)","additionalProperties":true},"ContractSchemaApi":{"type":"object","additionalProperties":true,"properties":{"methods":{"type":"object","additionalProperties":true}},"required":["methods"]}}}}
```

## The NonceResponse object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"NonceResponse":{"type":"object","additionalProperties":false,"properties":{"nonce":{"$ref":"#/components/schemas/Hex32"}},"required":["nonce"]},"Hex32":{"type":"string","description":"32-byte hex string (64 hex chars)","pattern":"^[0-9a-fA-F]{64}$"}}}}
```

## The TxContextMsb object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"TxContextMsb":{"type":"object","additionalProperties":false,"properties":{"networkId":{"type":"number"},"txv":{"$ref":"#/components/schemas/Hex32"},"iw":{"$ref":"#/components/schemas/Hex32"},"bs":{"$ref":"#/components/schemas/Hex32"},"mbs":{"$ref":"#/components/schemas/Hex32"},"operationType":{"type":"number"}},"required":["networkId","txv","iw","bs","mbs","operationType"]},"Hex32":{"type":"string","description":"32-byte hex string (64 hex chars)","pattern":"^[0-9a-fA-F]{64}$"}}}}
```

## The TxContextResponse object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"TxContextResponse":{"type":"object","additionalProperties":false,"properties":{"msb":{"$ref":"#/components/schemas/TxContextMsb"}},"required":["msb"]},"TxContextMsb":{"type":"object","additionalProperties":false,"properties":{"networkId":{"type":"number"},"txv":{"$ref":"#/components/schemas/Hex32"},"iw":{"$ref":"#/components/schemas/Hex32"},"bs":{"$ref":"#/components/schemas/Hex32"},"mbs":{"$ref":"#/components/schemas/Hex32"},"operationType":{"type":"number"}},"required":["networkId","txv","iw","bs","mbs","operationType"]},"Hex32":{"type":"string","description":"32-byte hex string (64 hex chars)","pattern":"^[0-9a-fA-F]{64}$"}}}}
```

## The PreparedCommand object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"PreparedCommand":{"type":"object","additionalProperties":true,"properties":{"type":{"type":"string","minLength":1,"maxLength":256},"value":{}},"required":["type","value"]}}}}
```

## The ContractTxRequest object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"ContractTxRequest":{"type":"object","additionalProperties":false,"properties":{"tx":{"$ref":"#/components/schemas/Hex32"},"prepared_command":{"$ref":"#/components/schemas/PreparedCommand"},"address":{"$ref":"#/components/schemas/Hex32"},"signature":{"$ref":"#/components/schemas/Hex64"},"nonce":{"$ref":"#/components/schemas/Hex32"},"sim":{"type":"boolean","default":false}},"required":["tx","prepared_command","address","signature","nonce"]},"Hex32":{"type":"string","description":"32-byte hex string (64 hex chars)","pattern":"^[0-9a-fA-F]{64}$"},"PreparedCommand":{"type":"object","additionalProperties":true,"properties":{"type":{"type":"string","minLength":1,"maxLength":256},"value":{}},"required":["type","value"]},"Hex64":{"type":"string","description":"64-byte hex string (128 hex chars)","pattern":"^[0-9a-fA-F]{128}$"}}}}
```

## The ContractTxResponse object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"ContractTxResponse":{"type":"object","additionalProperties":true,"properties":{"result":{}},"required":["result"]}}}}
```

## The StateResponse object

```json
{"openapi":"3.0.3","info":{"title":"trac-peer RPC (HTTP)","version":"1.0.0"},"components":{"schemas":{"StateResponse":{"type":"object","additionalProperties":false,"properties":{"key":{"type":"string"},"confirmed":{"type":"boolean"},"value":{}},"required":["key","confirmed","value"]}}}}
```


# Wallet and dApp

## Wallet + dApp Integration

This page focuses on “Ethereum-style” connectivity:

* dApps discover a `trac-peer` via a URL
* dApps fetch schema + context from the peer RPC
* wallets sign (they do not run peers)

### What a wallet signs (contract tx)

A `trac-peer` contract transaction is an MSB operation of `type = 12`.

The wallet signs a 32-byte hash (`tx`) computed from:

* MSB tx context (from `GET /v1/contract/tx/context`)
* the typed command `{ type, value }` (hashed as `ch`)
* a nonce (`in`)

Important: this is **not** a TNK transfer. There is no `to`/`amount` field in a contract tx.

Users can still pay MSB fees for the tx.

### Required RPC reads for signing

1. Contract schema (what exists):

* `GET /v1/contract/schema`

2. Nonce:

* `GET /v1/contract/nonce`

3. Tx context:

* `GET /v1/contract/tx/context`

This returns (names as used in MSB payloads):

* `networkId`
* `txv`
* `iw`
* `bs` (subnet bootstrap)
* `mbs` (MSB bootstrap)
* `operationType` (currently `12`)

### Wallet signing payload shape (recommended)

The wallet extension API used in this workspace exposes `window.tracnetwork`.

For contract tx signing, a typical payload is:

```json
{
  "prepared_command": { "type": "catch", "value": {} },
  "nonce": "<hex32>",
  "context": {
    "networkId": 918,
    "txv": "<hex32>",
    "iw": "<hex32>",
    "bs": "<hex32>",
    "mbs": "<hex32>",
    "operationType": 12
  }
}
```

The wallet should:

* compute `ch = blake3(JSON.stringify(prepared_command))`
* compute `tx = blake3(createMessage(networkId, txv, iw, ch, bs, mbs, nonce, operationType))`
* sign `tx` bytes with the active account’s private key

The dApp should also read the user’s public key (hex) via `window.tracnetwork.getPublicKey()` and use it as the `address` field when calling `POST /v1/contract/tx`.

### Simulate first, then broadcast

Recommended client flow:

1. submit the signed payload with `sim=true`
2. if OK, submit the same payload with `sim=false`

Both are `POST /v1/contract/tx`.

If `sim=true` fails due to missing MSB entry or insufficient fee balance, do not broadcast.

### Security model notes

* The peer validates signatures and MSB constraints. A dApp cannot “swap” the command type/value after the wallet signs, because that changes `ch` and `tx`.
* A contract tx cannot transfer TNK to a recipient. If you ever add native transfer support, it must be a different MSB operation type and the wallet UI must surface `to/amount`.


# App dev

## App Development (Protocol + Contract)

In Trac, an “app” is a subnet:

* the subnet is a P2P ordered log
* the contract is executed locally on every node from that ordered log
* MSB settlement is used as the global source of truth for tx finality and fee checks

This repo ships a demo app:

* `dev/tuxemonProtocol.js`
* `dev/tuxemonContract.js`

### 1) Protocol: map user input into `{ type, value }`

Protocols extend `src/artifacts/protocol.js` and should implement:

* `mapTxCommand(commandString)`

This is used by the interactive CLI `/tx --command "..."`.

dApps generally skip string parsing and send a structured `prepared_command` directly.

### 2) Contract: deterministic state machine

Contracts extend `src/artifacts/contract.js`.

Important rules:

* contracts must be deterministic (no network IO, no system time)
* all nodes execute the same ordered ops and must reach the same result

The contract uses a key/value state store. App-defined state should live under:

* `app/<your-app>/...`

### 3) Exposing “ABI-like” contract schema

Wallets/dApps need to know which tx types exist and what their input shapes are.

`trac-peer` exposes a discovery document at:

* `GET /v1/contract/schema`

The base contract supports metadata registration:

* `addFunction(type)` — declares a tx type exists (untyped inputs)
* `addSchema(type, schema)` — declares a tx type + a validator schema (preferred)

If you don’t register schemas, clients can still submit txs, but they’ll have to treat `value` as opaque.

### 4) Read APIs (Protocol API)

The protocol instance exposes `protocol.api`, which is intended for read/query methods.

Those methods can be reflected in the RPC schema so dApps can discover them (similar to “read-only RPC calls” in other ecosystems).

### 5) Where to wire your app

The default runner `scripts/run-peer.mjs` currently wires the demo app.

For your own app:

1. add your protocol/contract under `dev/`
2. create a new runner script that imports them
3. run that runner with the same MSB/subnet flags

See `intercom` in this workspace for a reference project that uses `trac-peer` as a dependency.

*


# References/Examples

These repos in the workspace are useful references for building on `trac-peer`:

### Reference apps

* `intercom/`
  * Shows a real project that depends on `trac-peer` and builds features/contracts on top of it.
  * See: [`intercom/SKILL.md`](https://github.com/Trac-Systems/intercom/blob/main/SKILL.md)
  * In-repo walkthrough: [Intercom example](/documentation/developers/mainnet/dapp-developer-guide/references-examples/intercom)
* `trac-dapp-example/`
  * A simple Next.js dApp that talks to a peer RPC and uses the wallet extension to sign contract txs.
  * In-repo walkthrough: [trac-dapp-example](/documentation/developers/mainnet/dapp-developer-guide/references-examples/trac-dapp-example)

### Wallet extension

* `tap-wallet-extension/`
  * Injects `window.tracnetwork` and implements `tracSignTx` approvals.


# Intercom

## Example App: `intercom/` (Built on `trac-peer`)

`intercom/` is a reference “real app” built on top of the `trac-peer` runtime.

It’s useful if you want to see what a production-ish subnet app repo looks like: a runner that boots MSB + peer, a contract/protocol pair, and a couple of optional features (sidechannels, timer).

### What Intercom demonstrates

* **How to ship an app**: a repo that depends on `trac-peer` and wires up `Protocol + Contract + Features`.
* **How to run multi-peer subnets**: bootstrap an “app network”, then join more peers.
* **Roles**: admin, writers, and indexers (durability vs cost/ops).
* **Sidechannels**: fast, ephemeral P2P messaging alongside the contract state machine.

### Get Intercom

Clone the repo:

```sh
git clone https://github.com/Trac-Systems/intercom
```

### Prerequisites

* **Node.js**: Intercom is intended to run on **Node 22+** (prefer 22/23; avoid 24 if you hit Pear/native issues).
* **Pear runtime**:

```sh
npm install -g pear
pear -v
```

### Install

```sh
cd ../intercom
npm install
```

For full Intercom operational guidance (first-run decisions, security defaults, etc.), also read [`../intercom/SKILL.md`](https://github.com/Trac-Systems/intercom/blob/main/SKILL.md).

### Create a new Intercom subnet (bootstrap / admin peer)

Pick a subnet channel name (this is the app’s rendezvous string) and start the first peer:

```sh
cd ../intercom
pear run . admin --subnet-channel my-intercom-app
```

On startup, Intercom prints the important bits you’ll need later:

* `Peer subnet bootstrap: <hex32>` — share this with joiners (also persisted to `stores/admin/subnet-bootstrap.hex`)
* `Peer pubkey (hex): <hex32>` — this is the admin peer’s public key (used for `/add_admin`)
* `Peer writer key (hex): <hex32>` — the admin peer’s writer key (used for `/add_writer` / `/add_indexer`)
* `MSB network bootstrap: <hex32>` + `MSB channel: <string>` — the embedded MSB node identifiers Intercom is using

#### First-time admin setup (required on new subnets)

On a brand-new subnet, you must set the subnet admin once, on the bootstrap node:

```txt
/add_admin --address "<ADMIN_PEER_PUBLICKEY_HEX32>"
```

Verify:

```txt
/get --key admin --confirmed false
```

### Start joiners (alice + bob)

Joiners must use the same subnet channel and the bootstrap’s subnet bootstrap hex:

```sh
cd ../intercom
pear run . alice --subnet-channel my-intercom-app --subnet-bootstrap <ADMIN_SUBNET_BOOTSTRAP_HEX32>
pear run . bob   --subnet-channel my-intercom-app --subnet-bootstrap <ADMIN_SUBNET_BOOTSTRAP_HEX32>
```

Each joiner prints its own:

* `Peer pubkey (hex)` (identity)
* `Peer writer key (hex)` (what the admin approves as writer/indexer)

### Writers and indexers (recommended guidance)

Intercom itself is a non-financial app example. Rule of thumb:

* **Non-financial apps**: 1 indexer can be enough.
* **Financial / “value” apps**: run **3 indexers**, ideally operated by different parties and in different locations.
* The **first indexer is usually the admin** peer.

#### Gated (default): admin approves writers/indexers

On the admin peer:

```txt
/add_writer --key "<JOINER_WRITER_KEY_HEX32>"
```

To make a peer an indexer (durable availability for reads / indexing):

```txt
/add_indexer --key "<JOINER_WRITER_KEY_HEX32>"
```

#### Open apps: auto-add writers

On the admin peer:

```txt
/set_auto_add_writers --enabled 1
```

### Sidechannels (fast P2P messaging)

Intercom always joins the **entry channel**:

```txt
0000intercom
```

You can join additional channels at runtime:

```txt
/sc_join --channel "team-room"
```

Send:

```txt
/sc_send --channel "team-room" --message "hello from alice"
```

Inspect:

```txt
/sc_stats
```

#### Sidechannel flags supported by Intercom’s runner

Intercom’s `index.js` supports (via flags or env):

* `--sidechannels "a,b,c"` (or `--sidechannel "a,b,c"`) — join extra channels at startup (entry channel is always joined)
* `--sidechannel-debug 1` — verbose logs
* `--sidechannel-max-bytes <n>` — payload size guard
* `--sidechannel-allow-remote-open 0|1` — accept/reject remote open requests (default: on)
* `--sidechannel-auto-join 0|1` — auto-join requested channels (default: off)

Example:

```sh
pear run . alice --subnet-channel my-intercom-app --subnet-bootstrap <ADMIN_SUBNET_BOOTSTRAP_HEX32> \
  --sidechannels "team-room,alerts" \
  --sidechannel-debug 1
```

### RPC (wallet / dApp connectivity)

Intercom is a **terminal-first** reference app. Its runner (`../intercom/index.js`) starts the interactive terminal and sidechannels, but it does **not** expose the `trac-peer` HTTP RPC server by default.

If you want an app that wallets/dApps can talk to over HTTP:

* follow the `trac-peer` RPC docs (`docs/04-rpc.md`, `docs/16-peer-rpc-reference.md`)
* add RPC wiring to your app runner (similar to how `trac-peer/scripts/run-peer.mjs` does it)

### Using your local `trac-peer/` checkout (optional, for contributors)

Intercom pins `trac-peer` by commit. If you want Intercom to use *your local* `trac-peer/` checkout while developing:

1. Edit `../intercom/package.json`:
   * change the `trac-peer` dependency to `file:../trac-peer`
2. Reinstall:

```sh
cd ../intercom
rm -rf node_modules package-lock.json
npm install
```

### Where to look in code (Intercom repo)

* Runner: [`intercom/index.js`](https://github.com/Trac-Systems/intercom/blob/main/index.js)
* Protocol: [`intercom/contract/protocol.js`](https://github.com/Trac-Systems/intercom/blob/main/contract/protocol.js)
* Contract: [`intercom/contract/contract.js`](https://github.com/Trac-Systems/intercom/blob/main/contract/contract.js)
* Sidechannels: [`intercom/features/sidechannel/index.js`](https://github.com/Trac-Systems/intercom/blob/main/features/sidechannel/index.js)
* Timer feature example: [`intercom/features/timer/index.js`](https://github.com/Trac-Systems/intercom/blob/main/features/timer/index.js)

Intercom’s own guide:

* [`intercom/SKILL.md`](https://github.com/Trac-Systems/intercom/blob/main/SKILL.md)


# Trac dApp Example

## Example dApp: `trac-dapp-example` (Wallet + Peer RPC)

This workspace contains a minimal reference dApp that demonstrates the intended client flow:

* dApp talks to a `trac-peer` HTTP RPC (a URL)
* dApp fetches schema + nonce + tx context
* wallet signs the contract tx (`window.tracnetwork.signTracTx(...)`)
* dApp submits `sim=true` then `sim=false`
* dApp reads contract state via `GET /v1/state`

In this workspace, that dApp lives in the folder:

* `trac-dapp-example/`

For documentation purposes we refer to it as [**`trac-dapp-example`**](https://github.com/Trac-Systems/trac-dapp-example) (it is a “catch creatures” demo).

***

### 1) Start a peer RPC node (the backend the dApp talks to)

You need a `trac-peer` node running with RPC enabled.

Example:

```sh
cd trac-peer

npm run peer:run -- \
  --msb-bootstrap=<MSB_BOOTSTRAP_HEX32> \
  --msb-channel=<MSB_CHANNEL> \
  --peer-store-name=peer-rpc \
  --msb-store-name=peer-rpc-msb \
  --subnet-channel=tuxedex-v1 \
  --rpc \
  --api-tx-exposed \
  --rpc-host 127.0.0.1 \
  --rpc-port 5001
```

Quick checks:

```sh
curl -s http://127.0.0.1:5001/v1/health | jq
curl -s http://127.0.0.1:5001/v1/contract/schema | jq
curl -s http://127.0.0.1:5001/v1/contract/tx/context | jq
```

Notes:

* `--api-tx-exposed` is required for the dApp to submit `POST /v1/contract/tx`.
* The peer must have enough MSB fee balance for contract txs; fund its printed `Peer MSB address` on MSB.

***

### 2) Install and run the dApp

```sh
cd trac-pokemon-hack
npm install
```

The Next.js server proxies requests to the peer RPC via env vars:

```sh
UPSTREAM_PROTOCOL=http \
UPSTREAM_HOST=127.0.0.1 \
UPSTREAM_PORT=5001 \
UPSTREAM_PREFIX=/v1 \
npm run dev
```

Open:

* `http://127.0.0.1:3000`

***

### 3) Wallet requirements

The dApp expects a browser wallet extension that injects:

* `window.tracnetwork`

and supports:

* `requestAccount()`
* `getAddress()`
* `getPublicKey()`
* `signTracTx(contractTx)`

In this workspace, see:

* `tap-wallet-extension/`

***

### 4) What happens when you click “Catch”

The dApp:

1. calls `GET /v1/contract/schema` (checks the contract supports `catch`)
2. calls `GET /v1/contract/nonce`
3. calls `GET /v1/contract/tx/context`
4. builds the wallet signing payload (`contractTx`) with:
   * `{ prepared_command, nonce, context }`
5. calls `window.tracnetwork.signTracTx(contractTx)` to get `{ tx, signature }`
6. calls `POST /v1/contract/tx` with `sim=true`
7. calls `POST /v1/contract/tx` with `sim=false`
8. polls state under:
   * `app/tuxedex/<pubKeyHex>` (unconfirmed view)

The peer’s demo contract writes the user’s “dex” under that key.

***

### 5) Troubleshooting

#### “Wallet extension not detected”

The dApp can’t find `window.tracnetwork`. Install/enable the wallet extension and reload the page.

#### “Requester address not found in state” / insufficient fee balance

Fund the **wallet’s MSB address** (the requester) on MSB so it can pay MSB fees for `type=12` contract txs.

Also ensure the peer’s own MSB address is funded so it can operate reliably.

#### Wrong peer / wrong contract

If `GET /v1/contract/schema` does not include `"catch"` in `contract.txTypes`, you’re pointing the dApp at a peer running a different contract/app.


# Troubleshooting

### “Missing MSB network params”

You must pass both:

* `--msb-bootstrap=<hex32>`
* `--msb-channel=<string>`

When using npm scripts, ensure you include the second `--`:

```sh
npm run peer:run -- --msb-bootstrap=<hex32> --msb-channel=<string>
```

### `zsh: command not found: --msb-channel=...`

This happens when the flag is on a new line without a `\` continuation.

Correct:

```sh
npm run peer:run -- \
  --msb-bootstrap=<hex32> \
  --msb-channel=<string>
```

### “Requester address not found in state”

Your peer MSB address must exist in MSB state and have funds to pay fees.

Fix:

* copy `Peer MSB address: trac1...` from peer logs
* transfer TNK to it from a funded MSB node

### “Subnet deployment broadcast failed”

Common causes:

* peer MSB address has no funds (fee check fails)
* MSB network params are wrong (bootstrap/channel mismatch)

### Subnet is read-only (`Subnet writable: false`)

You started a node with a keypair that does not match the subnet bootstrap writer.

If you intended to create a new subnet:

* delete `stores/<peer-store>/subnet-bootstrap.hex` and rerun

If you intended to join an existing subnet:

* ensure `--subnet-bootstrap` matches the existing subnet bootstrap

### Pear PATH warning

If Pear prints:

> prepend pear bin to PATH

follow the message once so `pear` runs directly.

### “Contract does not support ”

The demo contract supports only `catch`.

If you send another tx type, subnet apply will ignore it and it will not be indexed.


# Production notes

This page captures operational guidance for running `trac-peer` “for real” (public URLs, wallets/dApps, and multiple subnets).

### 1) Security boundaries (what is safe to expose)

`trac-peer` has two user classes:

* **Operator** (node owner): uses the interactive CLI to administer a subnet (admin, writer/indexer management, chat moderation, deployment).
* **Client** (wallet/dApp): uses HTTP RPC to read schema/state and optionally submit signed contract txs.

Design rule:

* **Do not expose operator actions via HTTP**.
* Only expose **wallet/dApp endpoints** via HTTP.

In this repo, the public RPC is intentionally limited to:

* health/status
* schema/context/nonce
* state reads
* tx submission (only when explicitly enabled)

### 2) RPC exposure (`--rpc` + `--api-tx-exposed`)

Start RPC:

```sh
npm run peer:run -- --rpc --rpc-host 127.0.0.1 --rpc-port 5001 ...
```

Tx submission is **opt-in**:

* `--api-tx-exposed` (or `PEER_API_TX_EXPOSED=1`)

If you start without `--rpc`, `--api-tx-exposed` is ignored.

#### CORS

Set `--rpc-allow-origin` (defaults to `*`).

For production, prefer a strict origin list (or terminate behind a reverse proxy that handles CORS).

#### Body limits

Request bodies are capped (default `1_000_000` bytes).

Override with:

* `--rpc-max-body-bytes <n>`

Keep this bounded; large bodies can become a DoS vector.

### 3) Recommended deployment topology

Typical patterns:

* **One public RPC per subnet/app** (a “gateway peer”), behind TLS and rate limits.
* **Multiple internal peers** to increase redundancy/availability (not necessarily exposed publicly).

Notes:

* A wallet needs the **peer URL** to fetch subnet state and tx details.
* If you plan to run many subnets/apps, you’ll eventually want a registry/explorer that maps “app identity” → peer URLs.

#### Indexers (how many, and why)

Subnets have two important Autobase roles:

* **writers**: can append subnet ops
* **indexers**: participate in linearization/indexing (what advances the subnet signed/confirmed view)

Rule of thumb:

* **Non-financial apps**: run **1 indexer**
* **Financial / “value” apps**: run **3 indexers**

For 3-indexer deployments, prefer:

* different datacenters/regions
* different authorities/teams (if possible)

Common practice:

* the **first indexer is the admin** (the initial bootstrap peer)

### 4) Reverse proxy + TLS (recommended)

Run the peer RPC on localhost and expose it via:

* nginx / Caddy / Cloudflare / API gateway

Why:

* TLS termination
* request size limits
* rate limiting
* access logs
* origin restrictions

### 5) Key management

Each peer has:

* MSB client keypair: `stores/<msb-store>/db/keypair.json`
* subnet peer keypair: `stores/<peer-store>/db/keypair.json`

Rules:

* Treat these files like private keys.
* Back up stores if you need persistence across restarts.
* Rotating keys effectively creates a new identity (and may lose admin/writer privileges).

### 6) Fees and “what can a contract do”

Contract transactions are MSB operations of `type = 12`.

* They **can** spend MSB fees (the requester must have an MSB entry and enough fee balance).
* They **cannot** transfer TNK to an arbitrary recipient (no `to/amount` in type-12 contract tx).

If you ever add native TNK transfer support, it must be a different MSB operation type and should be clearly surfaced in wallet UI (recipient/amount).

### 7) Observability (what to monitor)

On the peer CLI:

* `/stats` — subnet writer/indexer state, connectivity, DAG lengths
* `/msb` (demo protocol command) — prints MSB txv + lengths + fee + peer MSB balance

On RPC:

* `GET /v1/status` — peer + embedded MSB view summary

Recommended to monitor:

* RPC process uptime
* memory and CPU
* swarm connection counts
* subnet signedLength advancing
* MSB signedLength advancing (from embedded MSB client)

### 8) Confirmed vs unconfirmed reads

`GET /v1/state?confirmed=...` refers to the **subnet** view:

* `confirmed=true`: subnet signed view
* `confirmed=false`: latest local view

This is not identical to “MSB finality”; it reflects subnet indexing/signed length.

Client guidance:

* Use `confirmed=false` for fast UI updates.
* Use `confirmed=true` for “final” UI states if you require subnet-signed snapshots.

### 9) Rate limiting and abuse prevention

If you expose `POST /v1/contract/tx` publicly:

* enforce rate limits per IP/origin
* enforce burst limits
* keep request size bounded
* consider requiring a wallet connect / origin allowlist at the application layer

Even with signature validation, the RPC can be abused for compute-heavy simulations.

### 10) Upgrades / compatibility

For production, pin versions:

* `trac-peer`
* `trac-msb`
* `trac-wallet`

When upgrading:

* validate tx signing preimage compatibility (`/v1/contract/tx/context` fields)
* validate RPC endpoint compatibility
* validate contract schema compatibility (`/v1/contract/schema`)


# Main Settlement Bus

To run a self-hosted mainnet RPC, please follow the instructions in the [README](https://github.com/Trac-Systems/main_settlement_bus) of the MainSettlementBus repository. \
\
If you choose to run the process through a command instead of building a Docker image, we recommend using PM2 to manage and run your RPC in the background. You can find it [here](https://pm2.io/docs/runtime/guide/installation/).


# RPC API v1

Reference documentation for the public v1 RPC endpoints on Trac Network mainnet.

Use these endpoints to read ledger state and submit transactions.

### Network State

## Get current TXV

> Returns the current transaction validity hash (\`txv\`) as hex.

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"tags":[{"name":"Network"}],"servers":[{"url":"https://tracapi.trac.network","description":"Trac Network Mainnet"}],"paths":{"/v1/txv":{"get":{"tags":["Network"],"summary":"Get current TXV","description":"Returns the current transaction validity hash (`txv`) as hex.","responses":{"200":{"description":"TXV returned successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TxvResponse"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"TxvResponse":{"type":"object","required":["txv"],"properties":{"txv":{"type":"string"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Error message describing the failure."}}}},"responses":{"InternalServerError":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}
```

#### Get fee

Returns the fee used for transaction submission.

## GET /v1/fee

> Get current transaction fee

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"tags":[{"name":"Network"}],"servers":[{"url":"https://tracapi.trac.network","description":"Trac Network Mainnet"}],"paths":{"/v1/fee":{"get":{"tags":["Network"],"summary":"Get current transaction fee","responses":{"200":{"description":"Fee returned successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeeResponse"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"FeeResponse":{"type":"object","required":["fee"],"properties":{"fee":{"type":"string"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Error message describing the failure."}}}},"responses":{"InternalServerError":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}
```

#### Get confirmed length

Returns the current confirmed ledger length.

## GET /v1/confirmed-length

> Get confirmed ledger length

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"tags":[{"name":"Network"}],"servers":[{"url":"https://tracapi.trac.network","description":"Trac Network Mainnet"}],"paths":{"/v1/confirmed-length":{"get":{"tags":["Network"],"summary":"Get confirmed ledger length","responses":{"200":{"description":"Confirmed length returned successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmedLengthResponse"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"ConfirmedLengthResponse":{"type":"object","required":["confirmed_length"],"properties":{"confirmed_length":{"type":"integer"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Error message describing the failure."}}}},"responses":{"InternalServerError":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}
```

#### Get unconfirmed length

Returns the current unconfirmed ledger length.

## GET /v1/unconfirmed-length

> Get unconfirmed ledger length

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"tags":[{"name":"Network"}],"servers":[{"url":"https://tracapi.trac.network","description":"Trac Network Mainnet"}],"paths":{"/v1/unconfirmed-length":{"get":{"tags":["Network"],"summary":"Get unconfirmed ledger length","responses":{"200":{"description":"Unconfirmed length returned successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnconfirmedLengthResponse"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"UnconfirmedLengthResponse":{"type":"object","required":["unconfirmed_length"],"properties":{"unconfirmed_length":{"type":"integer"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Error message describing the failure."}}}},"responses":{"InternalServerError":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}
```

### Wallet & Account

#### Get balance

Returns the current balance for an address.

## Get wallet balance

> Returns balance for the specified wallet address.\
> \
> \`confirmed\` query behavior:\
> \- missing: defaults to confirmed view\
> \- \`true\`: confirmed view\
> \- \`false\`: unconfirmed view\
> \- any other value: falls back to unconfirmed view<br>

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"tags":[{"name":"Wallet"}],"servers":[{"url":"https://tracapi.trac.network","description":"Trac Network Mainnet"}],"paths":{"/v1/balance/{address}":{"get":{"tags":["Wallet"],"summary":"Get wallet balance","description":"Returns balance for the specified wallet address.\n\n`confirmed` query behavior:\n- missing: defaults to confirmed view\n- `true`: confirmed view\n- `false`: unconfirmed view\n- any other value: falls back to unconfirmed view\n","parameters":[{"$ref":"#/components/parameters/AddressParam"},{"$ref":"#/components/parameters/ConfirmedQueryParamLenient"}],"responses":{"200":{"description":"Balance returned successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BalanceResponse"}}}},"400":{"description":"Missing address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"parameters":{"AddressParam":{"name":"address","in":"path","required":true,"description":"Wallet/account address.","schema":{"type":"string","minLength":1}},"ConfirmedQueryParamLenient":{"name":"confirmed","in":"query","required":false,"description":"Balance endpoint behavior:\n- `true` for confirmed view\n- `false` for unconfirmed view\n- omitted => confirmed view\n- any other value => treated as unconfirmed view\n","schema":{"type":"string","default":"true"}}},"schemas":{"BalanceResponse":{"type":"object","required":["address","balance"],"properties":{"address":{"type":"string"},"balance":{"type":"string"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Error message describing the failure."}}}},"responses":{"InternalServerError":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}
```

#### Get account details

Returns account details for an address.

## Get account details

> Returns account details (roles, keys, balances and license).\
> \
> \`confirmed\` must be exactly \`true\` or \`false\`.<br>

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"tags":[{"name":"Wallet"}],"servers":[{"url":"https://tracapi.trac.network","description":"Trac Network Mainnet"}],"paths":{"/v1/account/{address}":{"get":{"tags":["Wallet"],"summary":"Get account details","description":"Returns account details (roles, keys, balances and license).\n\n`confirmed` must be exactly `true` or `false`.\n","parameters":[{"$ref":"#/components/parameters/AddressParam"},{"$ref":"#/components/parameters/ConfirmedQueryParamStrict"}],"responses":{"200":{"description":"Account details returned successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountDetailsResponse"}}}},"400":{"description":"Invalid address or invalid `confirmed` parameter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"parameters":{"AddressParam":{"name":"address","in":"path","required":true,"description":"Wallet/account address.","schema":{"type":"string","minLength":1}},"ConfirmedQueryParamStrict":{"name":"confirmed","in":"query","required":false,"description":"Must be exactly `true` or `false`.","schema":{"type":"string","enum":["true","false"],"default":"true"}}},"schemas":{"AccountDetailsResponse":{"type":"object","required":["address","writingKey","isWhitelisted","isValidator","isIndexer","license","balance","stakedBalance"],"properties":{"address":{"type":"string"},"writingKey":{"description":"64-character hex writing key.\nCan be all zeros when no writer key is assigned.\n","type":"string","pattern":"^[A-Fa-f0-9]{64}$"},"isWhitelisted":{"type":"boolean"},"isValidator":{"type":"boolean"},"isIndexer":{"type":"boolean"},"license":{"type":"string","nullable":true},"balance":{"type":"string"},"stakedBalance":{"type":"string"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Error message describing the failure."}}}},"responses":{"InternalServerError":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}
```

### Transactions - Read

#### List transaction hashes

Returns transaction hashes for a confirmed-length range.

{% hint style="info" %}
The start length must be less than or equal to the end length. The range must fall within the current confirmed length.
{% endhint %}

## Get transaction hashes by confirmed-length range

> Returns transaction hashes in inclusive range.\
> \
> Rules:\
> \- both params must be integers\
> \- both params must be non-negative\
> \- end must be greater than or equal to start\
> \- max difference is 1000<br>

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"tags":[{"name":"Transactions"}],"servers":[{"url":"https://tracapi.trac.network","description":"Trac Network Mainnet"}],"paths":{"/v1/tx-hashes/{start_confirmed_length}/{end_confirmed_length}":{"get":{"tags":["Transactions"],"summary":"Get transaction hashes by confirmed-length range","description":"Returns transaction hashes in inclusive range.\n\nRules:\n- both params must be integers\n- both params must be non-negative\n- end must be greater than or equal to start\n- max difference is 1000\n","parameters":[{"$ref":"#/components/parameters/StartConfirmedLengthParam"},{"$ref":"#/components/parameters/EndConfirmedLengthParam"}],"responses":{"200":{"description":"Hashes returned successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TxHashesResponse"}}}},"400":{"description":"Invalid range parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"parameters":{"StartConfirmedLengthParam":{"name":"start_confirmed_length","in":"path","required":true,"description":"Start of confirmed-length range (inclusive).","schema":{"type":"integer","minimum":0}},"EndConfirmedLengthParam":{"name":"end_confirmed_length","in":"path","required":true,"description":"End of confirmed-length range (inclusive).","schema":{"type":"integer","minimum":0}}},"schemas":{"TxHashesResponse":{"type":"object","required":["hashes"],"properties":{"hashes":{"type":"array","items":{"$ref":"#/components/schemas/TxHashItem"}}}},"TxHashItem":{"type":"object","required":["hash","confirmed_length"],"properties":{"hash":{"type":"string"},"confirmed_length":{"type":"integer"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Error message describing the failure."}}}},"responses":{"InternalServerError":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}
```

#### Get transaction by hash

Returns the transaction payload by hash.

{% hint style="info" %}
If the endpoint supports a `confirmed` query parameter, use it to select the confirmed view. Use the parameter details shown in the operation for default behavior.
{% endhint %}

## Get confirmed transaction details

> Returns normalized details for confirmed transaction hash.\
> \
> Current handler behavior:\
> \- if not found, returns \`404\` with \`{ "txDetails": null }\`\
> \- hash format is not strictly validated at handler layer<br>

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"tags":[{"name":"Transactions"}],"servers":[{"url":"https://tracapi.trac.network","description":"Trac Network Mainnet"}],"paths":{"/v1/tx/{transactionHash}":{"get":{"tags":["Transactions"],"summary":"Get confirmed transaction details","description":"Returns normalized details for confirmed transaction hash.\n\nCurrent handler behavior:\n- if not found, returns `404` with `{ \"txDetails\": null }`\n- hash format is not strictly validated at handler layer\n","parameters":[{"$ref":"#/components/parameters/TransactionHashParamLoose"}],"responses":{"200":{"description":"Transaction details returned successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TxDetailsResponse"}}}},"404":{"description":"Transaction not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TxDetailsNotFoundResponse"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"parameters":{"TransactionHashParamLoose":{"name":"transactionHash","in":"path","required":true,"description":"Transaction hash.","schema":{"type":"string","minLength":1}}},"schemas":{"TxDetailsResponse":{"type":"object","required":["txDetails"],"properties":{"txDetails":{"$ref":"#/components/schemas/TxDetailsPayload"}}},"TxDetailsPayload":{"type":"object","description":"Normalized transaction payload.","required":["type","address"],"properties":{"type":{"type":"integer"},"address":{"type":"string"},"tro":{"$ref":"#/components/schemas/TxOperationPayload"},"txo":{"$ref":"#/components/schemas/TxOperationPayload"}},"oneOf":[{"required":["tro"]},{"required":["txo"]}],"additionalProperties":false},"TxOperationPayload":{"type":"object","description":"Normalized operation object (`tro` for transfer or `txo` for subnet tx).\nFields depend on operation type; unknown keys may also appear.\n","properties":{"tx":{"type":"string","description":"Transaction hash."},"txv":{"type":"string","description":"Transaction validity hash."},"to":{"type":"string","description":"Recipient address (transfer)."},"am":{"type":"string","description":"Amount in base units."},"in":{"type":"string","description":"Nonce."},"is":{"type":"string","description":"Signature."},"va":{"type":"string","description":"Validator address or encoded validator field."},"vn":{"type":"string","description":"Validator nonce or validator-related field."},"vs":{"type":"string","description":"Validator signature or validator-related field."},"iw":{"type":"string","description":"Writer/indexer related field."},"bs":{"type":"string","description":"Bootstrap/subnetwork identifier (for subnet tx)."}},"additionalProperties":true},"TxDetailsNotFoundResponse":{"type":"object","required":["txDetails"],"properties":{"txDetails":{"type":"object","nullable":true}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Error message describing the failure."}}}},"responses":{"InternalServerError":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}
```

#### Get transaction details

Returns the transaction plus decoded and enriched details.

**`txDetails.tro` example (full field set)**

Examples for `txDetails.tro` should include all fields below. This avoids “missing field” ambiguity in client code.

```json
{
  "tx": "<transaction-hash>",
  "txv": "<transaction-vailidity>",
  "to": "<to-address>",
  "am": "<amount>",
  "in": "<requester-nonce>",
  "is": "<requester-signature>",
  "va": "<validator-address>",
  "vn": "<validator-nonce>",
  "vs": "<validator-signature>"
}
```

## Get transaction details with confirmation metadata

> Returns transaction details with \`confirmed\_length\` and \`fee\`.\
> \
> \`confirmed\` must be exactly \`true\` or \`false\`.<br>

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"tags":[{"name":"Transactions"}],"servers":[{"url":"https://tracapi.trac.network","description":"Trac Network Mainnet"}],"paths":{"/v1/tx/details/{transactionHash}":{"get":{"tags":["Transactions"],"summary":"Get transaction details with confirmation metadata","description":"Returns transaction details with `confirmed_length` and `fee`.\n\n`confirmed` must be exactly `true` or `false`.\n","parameters":[{"$ref":"#/components/parameters/TransactionHashParamStrict"},{"$ref":"#/components/parameters/ConfirmedQueryParamStrict"}],"responses":{"200":{"description":"Transaction details returned successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TxDetailsWithMetaResponse"}}}},"400":{"description":"Invalid transaction hash or invalid `confirmed` parameter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Transaction not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"parameters":{"TransactionHashParamStrict":{"name":"transactionHash","in":"path","required":true,"description":"Transaction hash (64-char hex string).","schema":{"type":"string","pattern":"^[A-Fa-f0-9]{64}$"}},"ConfirmedQueryParamStrict":{"name":"confirmed","in":"query","required":false,"description":"Must be exactly `true` or `false`.","schema":{"type":"string","enum":["true","false"],"default":"true"}}},"schemas":{"TxDetailsWithMetaResponse":{"type":"object","required":["txDetails","confirmed_length","fee"],"properties":{"txDetails":{"$ref":"#/components/schemas/TxDetailsPayload"},"confirmed_length":{"type":"integer","description":"Confirmed ledger length (0 when transaction is not yet confirmed)."},"fee":{"type":"string"}}},"TxDetailsPayload":{"type":"object","description":"Normalized transaction payload.","required":["type","address"],"properties":{"type":{"type":"integer"},"address":{"type":"string"},"tro":{"$ref":"#/components/schemas/TxOperationPayload"},"txo":{"$ref":"#/components/schemas/TxOperationPayload"}},"oneOf":[{"required":["tro"]},{"required":["txo"]}],"additionalProperties":false},"TxOperationPayload":{"type":"object","description":"Normalized operation object (`tro` for transfer or `txo` for subnet tx).\nFields depend on operation type; unknown keys may also appear.\n","properties":{"tx":{"type":"string","description":"Transaction hash."},"txv":{"type":"string","description":"Transaction validity hash."},"to":{"type":"string","description":"Recipient address (transfer)."},"am":{"type":"string","description":"Amount in base units."},"in":{"type":"string","description":"Nonce."},"is":{"type":"string","description":"Signature."},"va":{"type":"string","description":"Validator address or encoded validator field."},"vn":{"type":"string","description":"Validator nonce or validator-related field."},"vs":{"type":"string","description":"Validator signature or validator-related field."},"iw":{"type":"string","description":"Writer/indexer related field."},"bs":{"type":"string","description":"Bootstrap/subnetwork identifier (for subnet tx)."}},"additionalProperties":true},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Error message describing the failure."}}}}}}
```

### Transactions - Write

#### Resolve transaction payloads (bulk)

Fetches multiple transaction payloads in one request.

{% hint style="info" %}
This endpoint enforces request and response size limits. Split requests into smaller batches when you hit limits.
{% endhint %}

## Get transaction payloads in bulk

> Returns payloads for provided transaction hash list.\
> \
> Limits:\
> \- max \`1500\` hashes per request\
> \- request body limit: \`1\_000\_000\` bytes\
> \- response body limit: \`2\_000\_000\` bytes<br>

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"tags":[{"name":"Transactions"}],"servers":[{"url":"https://tracapi.trac.network","description":"Trac Network Mainnet"}],"paths":{"/v1/tx-payloads-bulk":{"post":{"tags":["Transactions"],"summary":"Get transaction payloads in bulk","description":"Returns payloads for provided transaction hash list.\n\nLimits:\n- max `1500` hashes per request\n- request body limit: `1_000_000` bytes\n- response body limit: `2_000_000` bytes\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TxPayloadsBulkRequest"}}}},"responses":{"200":{"description":"Payloads returned successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TxPayloadsBulkResponse"}}}},"400":{"description":"Invalid request body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Request/response size limits exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"schemas":{"TxPayloadsBulkRequest":{"type":"object","required":["hashes"],"properties":{"hashes":{"type":"array","minItems":1,"maxItems":1500,"items":{"type":"string"}}}},"TxPayloadsBulkResponse":{"type":"object","required":["results","missing"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/TxPayloadBulkResultItem"}},"missing":{"type":"array","items":{"type":"string"}}}},"TxPayloadBulkResultItem":{"type":"object","required":["hash","payload"],"properties":{"hash":{"type":"string"},"payload":{"$ref":"#/components/schemas/TxDetailsPayload"}}},"TxDetailsPayload":{"type":"object","description":"Normalized transaction payload.","required":["type","address"],"properties":{"type":{"type":"integer"},"address":{"type":"string"},"tro":{"$ref":"#/components/schemas/TxOperationPayload"},"txo":{"$ref":"#/components/schemas/TxOperationPayload"}},"oneOf":[{"required":["tro"]},{"required":["txo"]}],"additionalProperties":false},"TxOperationPayload":{"type":"object","description":"Normalized operation object (`tro` for transfer or `txo` for subnet tx).\nFields depend on operation type; unknown keys may also appear.\n","properties":{"tx":{"type":"string","description":"Transaction hash."},"txv":{"type":"string","description":"Transaction validity hash."},"to":{"type":"string","description":"Recipient address (transfer)."},"am":{"type":"string","description":"Amount in base units."},"in":{"type":"string","description":"Nonce."},"is":{"type":"string","description":"Signature."},"va":{"type":"string","description":"Validator address or encoded validator field."},"vn":{"type":"string","description":"Validator nonce or validator-related field."},"vs":{"type":"string","description":"Validator signature or validator-related field."},"iw":{"type":"string","description":"Writer/indexer related field."},"bs":{"type":"string","description":"Bootstrap/subnetwork identifier (for subnet tx)."}},"additionalProperties":true},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Error message describing the failure."}}}}}}
```

#### Broadcast signed transaction

Broadcasts a signed transaction payload to the network.

{% hint style="warning" %}
This endpoint may return `429 Too Many Requests` when rate limited. Retry with backoff.
{% endhint %}

## Broadcast signed transaction

> Broadcasts a signed transaction payload.\
> \
> Request requirements:\
> \- body must be valid JSON\
> \- body must contain \`payload\`\
> \- \`payload\` must be valid Base64 string<br>

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"tags":[{"name":"Transactions"}],"servers":[{"url":"https://tracapi.trac.network","description":"Trac Network Mainnet"}],"paths":{"/v1/broadcast-transaction":{"post":{"tags":["Transactions"],"summary":"Broadcast signed transaction","description":"Broadcasts a signed transaction payload.\n\nRequest requirements:\n- body must be valid JSON\n- body must contain `payload`\n- `payload` must be valid Base64 string\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BroadcastTransactionRequest"}}}},"responses":{"200":{"description":"Transaction accepted and broadcasted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BroadcastTransactionResponse"}}}},"400":{"description":"Invalid request payload.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Broadcast retry limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"schemas":{"BroadcastTransactionRequest":{"type":"object","required":["payload"],"properties":{"payload":{"type":"string","description":"Base64-encoded signed transaction."}}},"BroadcastTransactionResponse":{"type":"object","required":["result"],"properties":{"result":{"$ref":"#/components/schemas/BroadcastResult"}}},"BroadcastResult":{"type":"object","required":["message","signedLength","unsignedLength","tx"],"properties":{"message":{"type":"string"},"signedLength":{"type":"integer"},"unsignedLength":{"type":"integer"},"tx":{"type":"string"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Error message describing the failure."}}}}}}
```

### Errors & Limits

#### Confirmed query behavior

Some read endpoints accept a `confirmed` query parameter. It selects a confirmed view versus an unconfirmed view. Treat it as a consistency knob for reads.

#### `tx-hashes` range constraints

Ranges are constrained by confirmed length. Invalid ranges should be treated as client errors. Request smaller windows if you hit server limits.

#### Bulk request limits

`/v1/tx-payloads-bulk` is designed for batching. Keep batches small and bounded.

#### Broadcast rate limiting

`/v1/broadcast-transaction` can respond with `429`. Back off and retry.

### Schemas

## The TxDetailsWithMetaResponse object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"TxDetailsWithMetaResponse":{"type":"object","required":["txDetails","confirmed_length","fee"],"properties":{"txDetails":{"$ref":"#/components/schemas/TxDetailsPayload"},"confirmed_length":{"type":"integer","description":"Confirmed ledger length (0 when transaction is not yet confirmed)."},"fee":{"type":"string"}}},"TxDetailsPayload":{"type":"object","description":"Normalized transaction payload.","required":["type","address"],"properties":{"type":{"type":"integer"},"address":{"type":"string"},"tro":{"$ref":"#/components/schemas/TxOperationPayload"},"txo":{"$ref":"#/components/schemas/TxOperationPayload"}},"oneOf":[{"required":["tro"]},{"required":["txo"]}],"additionalProperties":false},"TxOperationPayload":{"type":"object","description":"Normalized operation object (`tro` for transfer or `txo` for subnet tx).\nFields depend on operation type; unknown keys may also appear.\n","properties":{"tx":{"type":"string","description":"Transaction hash."},"txv":{"type":"string","description":"Transaction validity hash."},"to":{"type":"string","description":"Recipient address (transfer)."},"am":{"type":"string","description":"Amount in base units."},"in":{"type":"string","description":"Nonce."},"is":{"type":"string","description":"Signature."},"va":{"type":"string","description":"Validator address or encoded validator field."},"vn":{"type":"string","description":"Validator nonce or validator-related field."},"vs":{"type":"string","description":"Validator signature or validator-related field."},"iw":{"type":"string","description":"Writer/indexer related field."},"bs":{"type":"string","description":"Bootstrap/subnetwork identifier (for subnet tx)."}},"additionalProperties":true}}}}
```

## The TxPayloadsBulkResponse object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"TxPayloadsBulkResponse":{"type":"object","required":["results","missing"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/TxPayloadBulkResultItem"}},"missing":{"type":"array","items":{"type":"string"}}}},"TxPayloadBulkResultItem":{"type":"object","required":["hash","payload"],"properties":{"hash":{"type":"string"},"payload":{"$ref":"#/components/schemas/TxDetailsPayload"}}},"TxDetailsPayload":{"type":"object","description":"Normalized transaction payload.","required":["type","address"],"properties":{"type":{"type":"integer"},"address":{"type":"string"},"tro":{"$ref":"#/components/schemas/TxOperationPayload"},"txo":{"$ref":"#/components/schemas/TxOperationPayload"}},"oneOf":[{"required":["tro"]},{"required":["txo"]}],"additionalProperties":false},"TxOperationPayload":{"type":"object","description":"Normalized operation object (`tro` for transfer or `txo` for subnet tx).\nFields depend on operation type; unknown keys may also appear.\n","properties":{"tx":{"type":"string","description":"Transaction hash."},"txv":{"type":"string","description":"Transaction validity hash."},"to":{"type":"string","description":"Recipient address (transfer)."},"am":{"type":"string","description":"Amount in base units."},"in":{"type":"string","description":"Nonce."},"is":{"type":"string","description":"Signature."},"va":{"type":"string","description":"Validator address or encoded validator field."},"vn":{"type":"string","description":"Validator nonce or validator-related field."},"vs":{"type":"string","description":"Validator signature or validator-related field."},"iw":{"type":"string","description":"Writer/indexer related field."},"bs":{"type":"string","description":"Bootstrap/subnetwork identifier (for subnet tx)."}},"additionalProperties":true}}}}
```

## The TxPayloadBulkResultItem object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"TxPayloadBulkResultItem":{"type":"object","required":["hash","payload"],"properties":{"hash":{"type":"string"},"payload":{"$ref":"#/components/schemas/TxDetailsPayload"}}},"TxDetailsPayload":{"type":"object","description":"Normalized transaction payload.","required":["type","address"],"properties":{"type":{"type":"integer"},"address":{"type":"string"},"tro":{"$ref":"#/components/schemas/TxOperationPayload"},"txo":{"$ref":"#/components/schemas/TxOperationPayload"}},"oneOf":[{"required":["tro"]},{"required":["txo"]}],"additionalProperties":false},"TxOperationPayload":{"type":"object","description":"Normalized operation object (`tro` for transfer or `txo` for subnet tx).\nFields depend on operation type; unknown keys may also appear.\n","properties":{"tx":{"type":"string","description":"Transaction hash."},"txv":{"type":"string","description":"Transaction validity hash."},"to":{"type":"string","description":"Recipient address (transfer)."},"am":{"type":"string","description":"Amount in base units."},"in":{"type":"string","description":"Nonce."},"is":{"type":"string","description":"Signature."},"va":{"type":"string","description":"Validator address or encoded validator field."},"vn":{"type":"string","description":"Validator nonce or validator-related field."},"vs":{"type":"string","description":"Validator signature or validator-related field."},"iw":{"type":"string","description":"Writer/indexer related field."},"bs":{"type":"string","description":"Bootstrap/subnetwork identifier (for subnet tx)."}},"additionalProperties":true}}}}
```

## The TxPayloadsBulkRequest object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"TxPayloadsBulkRequest":{"type":"object","required":["hashes"],"properties":{"hashes":{"type":"array","minItems":1,"maxItems":1500,"items":{"type":"string"}}}}}}}
```

## The TxDetailsNotFoundResponse object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"TxDetailsNotFoundResponse":{"type":"object","required":["txDetails"],"properties":{"txDetails":{"type":"object","nullable":true}}}}}}
```

## The TxDetailsResponse object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"TxDetailsResponse":{"type":"object","required":["txDetails"],"properties":{"txDetails":{"$ref":"#/components/schemas/TxDetailsPayload"}}},"TxDetailsPayload":{"type":"object","description":"Normalized transaction payload.","required":["type","address"],"properties":{"type":{"type":"integer"},"address":{"type":"string"},"tro":{"$ref":"#/components/schemas/TxOperationPayload"},"txo":{"$ref":"#/components/schemas/TxOperationPayload"}},"oneOf":[{"required":["tro"]},{"required":["txo"]}],"additionalProperties":false},"TxOperationPayload":{"type":"object","description":"Normalized operation object (`tro` for transfer or `txo` for subnet tx).\nFields depend on operation type; unknown keys may also appear.\n","properties":{"tx":{"type":"string","description":"Transaction hash."},"txv":{"type":"string","description":"Transaction validity hash."},"to":{"type":"string","description":"Recipient address (transfer)."},"am":{"type":"string","description":"Amount in base units."},"in":{"type":"string","description":"Nonce."},"is":{"type":"string","description":"Signature."},"va":{"type":"string","description":"Validator address or encoded validator field."},"vn":{"type":"string","description":"Validator nonce or validator-related field."},"vs":{"type":"string","description":"Validator signature or validator-related field."},"iw":{"type":"string","description":"Writer/indexer related field."},"bs":{"type":"string","description":"Bootstrap/subnetwork identifier (for subnet tx)."}},"additionalProperties":true}}}}
```

## The TxDetailsPayload object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"TxDetailsPayload":{"type":"object","description":"Normalized transaction payload.","required":["type","address"],"properties":{"type":{"type":"integer"},"address":{"type":"string"},"tro":{"$ref":"#/components/schemas/TxOperationPayload"},"txo":{"$ref":"#/components/schemas/TxOperationPayload"}},"oneOf":[{"required":["tro"]},{"required":["txo"]}],"additionalProperties":false},"TxOperationPayload":{"type":"object","description":"Normalized operation object (`tro` for transfer or `txo` for subnet tx).\nFields depend on operation type; unknown keys may also appear.\n","properties":{"tx":{"type":"string","description":"Transaction hash."},"txv":{"type":"string","description":"Transaction validity hash."},"to":{"type":"string","description":"Recipient address (transfer)."},"am":{"type":"string","description":"Amount in base units."},"in":{"type":"string","description":"Nonce."},"is":{"type":"string","description":"Signature."},"va":{"type":"string","description":"Validator address or encoded validator field."},"vn":{"type":"string","description":"Validator nonce or validator-related field."},"vs":{"type":"string","description":"Validator signature or validator-related field."},"iw":{"type":"string","description":"Writer/indexer related field."},"bs":{"type":"string","description":"Bootstrap/subnetwork identifier (for subnet tx)."}},"additionalProperties":true}}}}
```

## The TxOperationPayload object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"TxOperationPayload":{"type":"object","description":"Normalized operation object (`tro` for transfer or `txo` for subnet tx).\nFields depend on operation type; unknown keys may also appear.\n","properties":{"tx":{"type":"string","description":"Transaction hash."},"txv":{"type":"string","description":"Transaction validity hash."},"to":{"type":"string","description":"Recipient address (transfer)."},"am":{"type":"string","description":"Amount in base units."},"in":{"type":"string","description":"Nonce."},"is":{"type":"string","description":"Signature."},"va":{"type":"string","description":"Validator address or encoded validator field."},"vn":{"type":"string","description":"Validator nonce or validator-related field."},"vs":{"type":"string","description":"Validator signature or validator-related field."},"iw":{"type":"string","description":"Writer/indexer related field."},"bs":{"type":"string","description":"Bootstrap/subnetwork identifier (for subnet tx)."}},"additionalProperties":true}}}}
```

## The TxHashesResponse object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"TxHashesResponse":{"type":"object","required":["hashes"],"properties":{"hashes":{"type":"array","items":{"$ref":"#/components/schemas/TxHashItem"}}}},"TxHashItem":{"type":"object","required":["hash","confirmed_length"],"properties":{"hash":{"type":"string"},"confirmed_length":{"type":"integer"}}}}}}
```

## The TxHashItem object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"TxHashItem":{"type":"object","required":["hash","confirmed_length"],"properties":{"hash":{"type":"string"},"confirmed_length":{"type":"integer"}}}}}}
```

## The BroadcastTransactionResponse object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"BroadcastTransactionResponse":{"type":"object","required":["result"],"properties":{"result":{"$ref":"#/components/schemas/BroadcastResult"}}},"BroadcastResult":{"type":"object","required":["message","signedLength","unsignedLength","tx"],"properties":{"message":{"type":"string"},"signedLength":{"type":"integer"},"unsignedLength":{"type":"integer"},"tx":{"type":"string"}}}}}}
```

## The BroadcastResult object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"BroadcastResult":{"type":"object","required":["message","signedLength","unsignedLength","tx"],"properties":{"message":{"type":"string"},"signedLength":{"type":"integer"},"unsignedLength":{"type":"integer"},"tx":{"type":"string"}}}}}}
```

## The BroadcastTransactionRequest object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"BroadcastTransactionRequest":{"type":"object","required":["payload"],"properties":{"payload":{"type":"string","description":"Base64-encoded signed transaction."}}}}}}
```

## The UnconfirmedLengthResponse object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"UnconfirmedLengthResponse":{"type":"object","required":["unconfirmed_length"],"properties":{"unconfirmed_length":{"type":"integer"}}}}}}
```

## The ConfirmedLengthResponse object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"ConfirmedLengthResponse":{"type":"object","required":["confirmed_length"],"properties":{"confirmed_length":{"type":"integer"}}}}}}
```

## The FeeResponse object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"FeeResponse":{"type":"object","required":["fee"],"properties":{"fee":{"type":"string"}}}}}}
```

## The TxvResponse object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"TxvResponse":{"type":"object","required":["txv"],"properties":{"txv":{"type":"string"}}}}}}
```

## The AccountDetailsResponse object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"AccountDetailsResponse":{"type":"object","required":["address","writingKey","isWhitelisted","isValidator","isIndexer","license","balance","stakedBalance"],"properties":{"address":{"type":"string"},"writingKey":{"description":"64-character hex writing key.\nCan be all zeros when no writer key is assigned.\n","type":"string","pattern":"^[A-Fa-f0-9]{64}$"},"isWhitelisted":{"type":"boolean"},"isValidator":{"type":"boolean"},"isIndexer":{"type":"boolean"},"license":{"type":"string","nullable":true},"balance":{"type":"string"},"stakedBalance":{"type":"string"}}}}}}
```

## The BalanceResponse object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"BalanceResponse":{"type":"object","required":["address","balance"],"properties":{"address":{"type":"string"},"balance":{"type":"string"}}}}}}
```

## The ErrorResponse object

```json
{"openapi":"3.0.3","info":{"title":"Trac Network RPC API","version":"1.0.2"},"components":{"schemas":{"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Error message describing the failure."}}}}}}
```


# Gasless Net (R1)

This section describes how to build on Trac Network Release 1 (R1).

R1 is ready for production and shares most of the features of the upcoming mainnet.&#x20;

However, Mainnet won't invalidate apps developed on R1 as this release is going to be guaranteed to stay maintained (Hypermall is built on it).

## Why Trac Network

Trac Network enables fast transaction finality and App3 capabilities, complementing the Web3 idea.

Trac Network smart contracts are called apps and are primarily focused on native app execution (Desktop/Mobile). Web3 compatibility is assured on app level and is highly configurable.

The app approach has consumers in mind first, because the typical crypto hoops to go through are drastically minimized.

Trac Network is a true peer-to-peer network and has self-custody built into its DNA across the board.

## The Network

Trac Network is a pure peer-to-peer crypto network. Each participant in the network can be connected with each other, share data and agree on smart contract consensus — directly, while operating their apps.

Instead of utilizing a blockchain, the peer-to-peer approach allows for supporting streams of transactions. The result is a generally favorable transaction finality as the consensus is being achieved on transaction level rather than waiting for blocks to finalize (transactions finalize individually).

The ledger is being shared across the network at all times, across all participants. However, this doesn't mean that everyone has to share a complete set of data. Ledger data is distributed sparsely, only requested when needed. Additionally, there are special network participants called "indexers" that can be best compared to archive nodes in the blockchain world.

This approach allows to push the original Web3 idea further and applies it to native Apps (App3):

* Each participant executes contracts, this includes users
* An app is a peer in the network that embeds the same contracts like everyone else
* Web3 can still be executed by peers exposing APIs to let wallets connect to and transact (similar to Full Nodes)

How that works on app level is described in [this section](/real-world-examples/hypermall) for the Hypermall.

Instances of Trac Network may be detached from each other, even from Mainnet, to create seperated subnets. In fact this is encouraged as these subnets will still support the decentralized peer-to-peer swarm and stay in touch with validators — the backbone of the network.

## Validators

Validators are special peers, technically operating on their own network, to accept transaction requests from other apps to verify. They also strengthen the general network connectivity by sheer presence as the have a monetary incentive to do so:

* In R1, validators may be incentivized by projects to validate their apps. An example is Hypermall, which rewards validators by sharing 50% of the trading fees to keep the network secured.
* On Mainnet, validators will earn $TNK as network asset and still maintain the ability to earn elsewhere in the network if it's profitable to them.

Validators do not run traditional proof-of-\* strategies. Instead, consensus is reached through smart contracts upon transaction validation. This keeps the footprint light and allows validators to focus on business opportunities with the ecosystem of the network.

## Development Process

The development process differs from typical blockchain engineering.&#x20;

We streamlined the development and deployment process to make it as easy as possible:

* The programming language is Javascript, the most popular and broadly used programming language
* The package system is Node.js compatible and allows for a variety of existing libraries to be used
* Apps can be executed and distributed decentralized as it utilizes the [Pears project ](https://pears.com/)as native runtime and for decentralized app distribution
* Existing validators can be "borrowed" from the main project but you can run an entire validator network yourself (with the organizational overhead this entails)


# Contracts

Learn how to create and deploy contracts on the Trac Network.

Smart Contracts consist of two elements:

1. The protocol — tells the peer how to pass transaction data and how to behave.
2. The contract — the actual contract logic, "listening" to the terms setup in the protocol.

### Anatomy of an example Protocol/Contract pair

Please read the code comments in the files below. Examples are taken from our [Example Contract ](https://github.com/Trac-Systems/trac-contract-example/)Github repo.

#### Protocol

```javascript
import {Protocol} from "trac-peer";

class SampleProtocol extends Protocol{
    /**
     * Extending from Protocol inherits its capabilities and allows you to define your own protocol.
     * The protocol supports the corresponding contract. Both files come in pairs.
     *
     * Instances of this class do NOT run in contract context. The constructor is only called once on Protocol
     * instantiation.
     *
     * this.peer: an instance of the entire Peer class, the actual node that runs the contract and everything else.
     * this.base: the database engine, provides await this.base.view.get('key') to get unsigned data (not finalized data).
     * this.options: the option stack passed from Peer instance.
     *
     * @param peer
     * @param base
     * @param options
     */
    constructor(peer, base, options = {}) {
        // calling super and passing all parameters is required.
        super(peer, base, options);
    }
    
    /**
     * The Protocol superclass ProtocolApi instance already provides numerous api functions.
     * You can extend the built-in api based on your protocol requirements.
     *
     * @returns {Promise<void>}
     */
    async extendApi(){
        this.api.getSampleData = function(){
            return 'Some sample data';
        }
    }
    
    /**
     * In order for a transaction to successfully trigger,
     * you need to create a mapping for the incoming tx command,
     * pointing at the contract function to execute.
     *
     * You can perform basic sanitization here, but do not use it to protect contract execution.
     * Instead, use the built-in schema support for in-contract sanitization instead
     * (Contract.addSchema() in contract constructor).
     *
     * @param command
     * @returns {{type: string, value: *}|null}
     */
    mapTxCommand(command){
        // prepare the payload
        let obj = { type : '', value : null };
        /*
        Triggering contract function in terminal will look like this:
    
        /tx --command 'something'
    
        You can also simulate a tx prior broadcast
    
        /tx --command 'something' --sim 1
    
        To programmatically execute a transaction from "outside",
        the api function "this.api.tx()" needs to be exposed by adding
        "api_tx_exposed : true" to the Peer instance options.
        Once exposed, it can be used directly through peer.protocol_instance.api.tx()
    
        Please study the superclass of this Protocol and Protocol.api to learn more.
        */
        if(command === 'something'){
            // type points at the "storeSomething" function in the contract.
            obj.type = 'storeSomething';
            // value can be null as there is no other payload, but the property must exist.
            obj.value = null;
            // return the payload to be used in your contract
            return obj;
        } else {
            /*
            now we assume our protocol allows to submit a json string with information
            what to do (the op) then we pass the parsed object to the value.
            the accepted json string can be executed as tx like this:
    
            /tx --command '{ "op" : "do_something", "some_key" : "some_data" }'
    
            Of course we can simulate this, as well:
    
            /tx --command '{ "op" : "do_something", "some_key" : "some_data" }' --sim 1
            */
            const json = this.safeJsonParse(command);
            if(json.op !== undefined && json.op === 'do_something'){
                obj.type = 'submitSomething';
                obj.value = json;
                return obj;
            }
        }
        // return null if no case matches.
        // if you do not return null, your protocol might behave unexpected.
        return null;
    }
    
    /**
     * Prints additional options for your protocol underneath the system ones in terminal.
     *
     * @returns {Promise<void>}
     */
    async printOptions(){
        console.log(' ');
        console.log('- Sample Commands:');
        console.log("- /print | use this flag to print some text to the terminal: '--text \"I am printing\"");
        // further protocol specific options go here
    }
    
    /**
     * Extend the terminal system commands and execute your custom ones for your protocol.
     * This is not transaction execution itself (though can be used for it based on your requirements).
     * For transactions, use the built-in /tx command in combination with command mapping (see above)
     *
     * @param input
     * @returns {Promise<void>}
     */
    async customCommand(input) {
        await super.tokenizeInput(input);
        if (this.input.startsWith("/print")) {
            const splitted = this.parseArgs(input);
            console.log(splitted.text);
        }
    }
}
export default SampleProtocol; 
```

#### Contract

```javascript
import {Contract} from 'trac-peer'

class SampleContract extends Contract {
    /**
     * Extending from Contract inherits its capabilities and allows you to define your own contract.
     * The contract supports the corresponding protocol. Both files come in pairs.
     *
     * Instances of this class run in contract context. The constructor is only called once on Peer
     * instantiation.
     *
     * Please avoid using the following in your contract functions:
     *
     * No try-catch
     * No throws
     * No random values
     * No http / api calls
     * No super complex, costly calculations
     * No massive storage of data.
     * Never, ever modify "this.op" or "this.value", only read from it and use safeClone to modify.
     * ... basically nothing that can lead to inconsistencies akin to Blockchain smart contracts.
     *
     * Running a contract on Trac gives you a lot of freedom, but it comes with additional responsibility.
     * Make sure to benchmark your contract performance before release.
     *
     * If you need to inject data from "outside", you can utilize the Feature class and create your own
     * oracles. Instances of Feature can be injected into the main Peer instance and enrich your contract.
     *
     * In the current version (Release 1), there is no inter-contract communication yet.
     * This means it's not suitable yet for token standards.
     * However, it's perfectly equipped for interoperability or standalone tasks.
     *
     * this.protocol: the peer's instance of the protocol managing contract concerns outside of its execution.
     * this.options: the option stack passed from Peer instance
     *
     * @param protocol
     * @param options
     */
    constructor(protocol, options = {}) {
        // calling super and passing all parameters is required.
        super(protocol, options);

        // simple function registration.
        // since this function does not expect value payload, no need to sanitize.
        // note that the function must match the type as set in Protocol.mapTxCommand()
        this.addFunction('storeSomething');

        // now we register the function with a schema to prevent malicious inputs.
        // the contract uses the schema generator "fastest-validator" and can be found on npmjs.org.
        //
        // Since this is the "value" as of Protocol.mapTxCommand(), we must take it full into account.
        // $$strict : true tells the validator for the object structure to be precise after "value".
        //
        // note that the function must match the type as set in Protocol.mapTxCommand()
        this.addSchema('submitSomething', {
            value : {
                $$strict : true,
                $$type: "object",
                op : { type : "string", min : 1, max: 128 },
                some_key : { type : "string", min : 1, max: 128 }
            }
        });

        // in preparation to add an external Feature (aka oracle), we add a loose schema to make sure
        // the Feature key is given properly. it's not required, but showcases that even these can be
        // sanitized.
        this.addSchema('feature_entry', {
            key : { type : "string", min : 1, max: 256 },
            value : { type : "any" }
        });

        // now we are registering the timer feature itself (see /features/time/ in package).
        // note the naming convention for the feature name <feature-name>_feature.
        // the feature name is given in app setup, when passing the feature classes.
        const _this = this;

        // this feature registers incoming data from the Feature and if the right key is given,
        // stores it into the smart contract storage.
        // the stored data can then be further used in regular contract functions.
        this.addFeature('timer_feature', async function(){
            if(false === _this.validateSchema('feature_entry', _this.op)) return;
            if(_this.op.key === 'currentTime') {
                if(null === await _this.get('currentTime')) console.log('timer started at', _this.op.value);
                await _this.put(_this.op.key, _this.op.value);
            }
        });

        // last but not least, you may intercept messages from the built-in
        // chat system, and perform actions similar to features to enrich your
        // contract. check the _this.op value after you enabled the chat system
        // and posted a few messages.
        this.messageHandler(async function(){
            console.log('message triggered contract', _this.op);
        });
    }

    /**
     * A simple contract function without values (=no parameters).
     *
     * Contract functions must be registered through either "this.addFunction" or "this.addSchema"
     * or it won't execute upon transactions. "this.addFunction" does not sanitize values, so it should be handled with
     * care or be used when no payload is to be expected.
     *
     * Schema is recommended to sanitize incoming data from the transaction payload.
     * The type of payload data depends on your protocol.
     *
     * This particular function does not expect any payload, so it's fine to be just registered using "this.addFunction".
     *
     * However, as you can see below, what it does is checking if an entry for key "something" exists already.
     * With the very first tx executing it, it will return "null" (default value of this.get if no value found).
     * From the 2nd tx onwards, it will print the previously stored value "there is something".
     *
     * It is recommended to check for null existence before using put to avoid duplicate content.
     *
     * As a rule of thumb, all "this.put()" should go at the end of function execution to avoid code security issues.
     *
     * Putting data is atomic, should a Peer with a contract interrupt, the put won't be executed.
     */
    async storeSomething(){
        const something = await this.get('something');

        console.log('is there already something?', something);

        if(null === something) {
            await this.put('something', 'there is something');
        }
    }

    /**
     * Now we are using the schema-validated function defined in the constructor.
     *
     * The function also showcases some of the handy features like safe functions
     * to prevent throws and safe bigint/decimal conversion.
     */
    async submitSomething(){
        // the value of some_key shouldn't be empty, let's check that
        if(this.value.some_key === ''){
            return new Error('Cannot be empty');
            // alternatively false for generic errors:
            // return false;
        }

        // of course the same works with assert (always use this.assert)
        this.assert(this.value.some_key !== '', new Error('Cannot be empty'));

        // btw, please use safeBigInt provided by the contract protocol's superclass
        // to calculate big integers:
        const bigint = this.protocol.safeBigInt("1000000000000000000");

        // making sure it didn't fail
        this.assert(bigint !== null);

        // you can also convert a bigint string into its decimal representation (as string)
        const decimal = this.protocol.fromBigIntString(bigint.toString(), 18);

        // and back into a bigint string
        const bigint_string = this.protocol.toBigIntString(decimal, 18);

        // let's clone the value
        const cloned = this.protocol.safeClone(this.value);

        // we want to pass the time from the timer feature.
        // since mmodifications of this.value is not allowed, add this to the clone instead for storing:
        cloned['timestamp'] = await this.get('currentTime');

        // making sure it didn't fail (be aware of false-positives if null is passed to safeClone)
        this.assert(cloned !== null);

        // and now let's stringify the cloned value
        const stringified = this.protocol.safeJsonStringify(cloned);

        // and, you guessed it, best is to assert against null once more
        this.assert(stringified !== null);

        // and guess we are parsing it back
        const parsed = this.protocol.safeJsonParse(stringified);

        // parsing the json is a bit different: instead of null, we check against undefined:
        this.assert(parsed !== undefined);

        // finally we are storing what address submitted the tx and what the value was
        await this.put('submitted_by/'+this.address, parsed.some_key);

        // printing into the terminal works, too of course:
        console.log('submitted by', this.address, parsed);
    }
}

export default SampleContract;
```


# Features

Learn how to utilize Features to act as multiplex oracle for your contracts.

Please read the code comments in the files below. Examples are taken from our [Example Contract ](https://github.com/Trac-Systems/trac-contract-example/)Github repo.

```javascript
// import the Feature superclass from the trac-peer package
import {Feature} from 'trac-peer';

export class Timer extends Feature {

    /**
    * Setup your Feature
    *
    * Instances of Features are passed to Peer instances.
    * See the Deployment section for more details.
    */
    constructor(peer, options = {}) {
        super(peer, options);
        this.update_interval = options.update_interval !== undefined &&
                                false === isNaN(parseInt(options.update_interval)) &&
                                parseInt(options.update_interval) > 0 ? parseInt(options.update_interval) : 60_000;
    }

    /**
    * start() is supposed to trigger the actual Feature execution.
    *
    * In the case of timers, an infinite event loop reads the latest
    * time every 10th second and appends it into the contract.
    *
    + The contract will then read this value as showcased in the previous
    * section.
    *
    * Appends from Features don't go through transactions but straight into
    * the contract.
    */
    async start(options = {}) {
        while(true){
            await this.append('currentTime', Date.now());
            await this.sleep(this.update_interval);
        }
    }

    // stop helps to shutdown and may be triggered by a custom Feature-handler
    async stop(options = {}) { }
}

export default Timer;
```


# Messaging

Trac Peers ship with their own chat/messaging infrastructure and messages can be handled by smart contract directly.

To enable messaging for your contract, the admin has to enble it first:

```
/set_chat_status --enabled 1
```

<figure><img src="/files/SjIqS9y505ev0p5VCtPi" alt=""><figcaption></figcaption></figure>

Once enabled, all connected peers may start to chat. Chat messages do not go through transactions but are directly sent to the contract:

```
/post --message "hi"
```

<figure><img src="/files/tWmw4pjXDwvSrsPCUSyV" alt=""><figcaption></figcaption></figure>

User may change their nickname:

```
/set_nick --nick "Peter"
```

<figure><img src="/files/V0SNzIMUKxauSxUllNKm" alt=""><figcaption></figcaption></figure>

As you can see, the message triggered the messageHandler() described in the [Contracts](/documentation/developers/gasless-net-r1/contracts) section.

The messaging engine in Trac Peers provides the foundation to create rich, social applications and the capability to "contractualize" the message flow or enhancing the messaging system. This is extremely powerful, especially combined when creating App3 and Web3 apps.

Thinkable use-cases are AI agents for example, that chat in behalf of their owners to trade on some app.

See the options for more built-in settings.&#x20;

<figure><img src="/files/zLtApfPTpK8GzsJMTFCC" alt=""><figcaption></figcaption></figure>


# Deployment

The deploment process of contracts is straightforward and free. There are no gas-costs for deployments. All you need is a machine that runs it and from there distribute.

Please read the code comments in the files below. Examples are taken from our [Example Contract ](https://github.com/Trac-Systems/trac-contract-example/)Github repo. We assume the example contract will be executed and the below helps you to get a better understanding.

Below is a typical configuration file that goes wit your app package. In the example below, there is no need to setup a custom Main Settlement Bus as we provided one already as a kind of testnet (gasless).

The configurable part consists of the Peer and Feature options.

### Setup

```javascript
import {getStorePath} from './src/functions.js';
import {App} from './src/app.js';
export * from 'trac-peer/src/functions.js'
import {default as SampleProtocol} from "./contract/protocol";
import {default as SampleContract} from "./contract/contract";
import {Timer} from "./features/timer/index.js";

console.log('Storage path:', getStorePath());

///// MSB SETUP
// To run this example, you don't need to create your own MSB
// Instead go with the options as-is. The below bootstrap is an MSB testnet (gasless).
const msb_opts = {};
msb_opts.bootstrap = 'cdcb126766cb2673bc14f3e91be61150504d0f97e5055bbc430193091fe96bba';
msb_opts.channel = '0000000000000000000000examplemsb';
msb_opts.store_name = getStorePath() + '/msb';

///// SAMPLE CONTRACT SETUP
// The sample contract needs to be deployed first.
// See the README.md for further information.
const peer_opts = {};
peer_opts.protocol = SampleProtocol;
peer_opts.contract = SampleContract;
peer_opts.bootstrap = '0000000000000000000000000000000000000000000000000000000000000000';
peer_opts.channel = '0000000000000000000000000example';
peer_opts.store_name = getStorePath() + '/example';
peer_opts.api_tx_exposed = true;
peer_opts.api_msg_exposed = true;

///// FEATURES
// Pass multiple features (aka oracles) to the peer and inject data into
// your contract. Can also go the other way, depending on how you need it.
// You may add as many Features as you wish.
// In /src/app.js, the Features are being executed by the admin (usually the Peer Bootstrap)
const timer_opts = {};
timer_opts.update_interval = 10_000;

export const app = new App(msb_opts, peer_opts, [
    {
        name : 'timer',
        class : Timer,
        opts : timer_opts
    }
]);
await app.start();
```

### Install

```
git clone git@github.com:Trac-Systems/trac-contract-example.git
```

While the Trac apps support native node-js, it is encouraged to use Pear:

```
cd trac-contract-example
npm install -g pear
npm install
pear run . store1
```

### Deployment

**Deploy Bootstrap (admin):**

* Start your app the first time and choose option 1)
* Copy and backup the seedphrase
* Copy the "Peer Writer" key from the Peer section (basically the contract address)
* With a text editor, open the file index.js in document root
* Replace the bootstrap address in the example section (not the MSB) with the copied writer address
* Choose a channel name (exactly 32 characters)
* Type /exit and hit enter, then run again: pear run . store1
* After the options appear, type "/add\_admin --address YourPeerWriterKey" and hit enter
* Your instance is now the Bootstrap and admin peer of your contract network.
* Keep your bootstrap node running
* For production contracts, it is strongly recommended to add a couple of indexers. See below.

**Running indexers (admin)**

* Install on different machines than the Bootstrap's (ideally different data centers) with the exact setup in index.js
* Upon start ("pear run . store1") copy the "Peer Writer" key
* In the Bootstrap node screen, add the indexer: "/add\_indexer --key TheIndexerWriterKey."
* You should see a success confirmation
* Usually 2 indexers on different locations are enough, we recommend 2 to max. 4 in addition to the Bootstrap

**Enable others to join and to transact:**

* By default, people cannot auto-join the contract network. The network admin (the Bootstrap in this case) can enable auto-join
* To enable auto-join, in the screen of the Bootstrap enter "/set\_auto\_add\_writers --enabled 1"
* Any other Peer joining with the exact same setup can join the network and execute contract functions and transactions.
* Users may join using the exact same setup in index.js and start using "pear run . store1"
* For more features, play around with the available system and chat options.


# Custom Validators

An MSB (Main Settlement Bus) is needed for any contract to operate that executes smart contract transactions. MSBs have to be populated (whitelisted) with validators. Each validator runs an instance of the MSB and awaits transactions from contracts (or better: contract peers).

A project in Release 1 of the Trac Network should therefore provide incentives for validators to actually validiate their contract network. Hypermall does this for example by letting validators earn 50% of the trading fee.

If you want to roll your own validator network, then check out below how to do this.  The current validator community in our [Discord](https://discord.com/invite/trac) might be interested in your project help validating.

### Install

```shell
git clone -b msb-r1 --single-branch git@github.com:Trac-Systems/main_settlement_bus.git
```

### Usage

While the MSB supports native node-js, it is encouraged to use Pear:

```js
cd main_settlement_bus
npm install -g pear
npm install
pear run . store1
```

**Deploy Bootstrap (admin):**

* Choose option 1)
* Copy and backup the seedphrase
* Copy the "MSB Writer" address
* With a text editor, open the file msb.mjs in document root
* Replace the bootstrap address with the copied writer address
* Choose a channel name (exactly 32 characters)
* Run again: pear run . store1
* After the options appear, type /add\_admin and hit enter
* Your instance is now the Bootstrap and admin peer, required to control validators
* Keep your bootstrap node running
* Strongly recommended: add a couple of nodes as writers

**Running indexers (admin)**

* Install on different machines than the Bootstrap's (ideally different data centers)
* Follow the "Running as validator" and then "Adding validators" procedures below
* Copy the MSB Writer address from your writer screen
* In your Bootstrap screen, add activate the new writers:
* /add\_indexer \<MSB Writer address (not the MSB address!)>
* You should see a success confirmation
* Usually 2 indexers on different locations are enough, we recommend 2 to max. 4 in addition to the Bootstrap

**Running as validator (first run):**

* Choose option 1)
* Copy and backup the seedphrase
* Copy the "MSB Address" after the screen fully loaded
* Hand your "MSB Address" over to the MSB admin for whitelisting
* Wait for the admin to announce the whitelist event
* In the screen type /add\_writer
* After a few seconds you should see your validator being added as a writer

**Adding validators (admin):**

* Open the file /Whitelist/pubkeys.csv with a text editor
* Add as man Trac Network addresses as you wish
* In the MSB screen, enter /add\_whitelist
* Wait for the listto be fully processed
* Inform your validator community being whitelisted


# Hypermall

HyperMall is the first application built on the Trac Network — a new kind of crypto infrastructure

designed for speed, self-custody, simplicity, and real-world usability. Unlike traditional

blockchains that rely on blocks and confirmations, the Trac Network processes transactions in a

continuous stream. That means HyperMall can offer instant interactions, no waiting, and a

smoother experience for both developers and users.

At its core, HyperMall is a decentralized exchange — but it doesn’t feel like one. From the

user's perspective, it behaves like a centralized exchange with orderbooks: fast, responsive,

and intuitive. No wallets to connect. No gas fees. Just seamless, permissionless trading through

a downloadable app that runs natively on your PC.

Unlike most crypto apps, HyperMall isn’t a web app. It’s a desktop application available for

Windows, Linux, and macOS, with a fully integrated wallet built in. This design not only boosts

performance and user experience — it also unlocks new advantages for transparency and

security, which we’ll explore in the sections ahead.


# Security in HyperMall

Security is built into every layer of the HyperMall experience — not as an afterthought, but as a

core design principle. Rather than forcing users into a browser-based model, HyperMall starts

with a native desktop application that runs locally on your PC. This approach minimizes attack

surfaces, avoids the risks of browser extensions and third-party injection, and gives users a

tightly controlled environment for managing assets.

The wallet is fully integrated into the app itself. That means no need to connect external wallets,

no pop-ups, and no web-based interactions that could be spoofed or hijacked. Signing happens

locally, and your keys never leave your device.

On the backend, HyperMall uses the Trac Network — a headless (blockchain-less),

stream-based crypto system. There are no blocks to wait for and no centralized intermediaries.

Transactions are fast, deterministic, and less vulnerable to manipulation.

In the future, we plan to support server-mode deployments. That will allow HyperMall nodes to

provide a browser-accessible interface, opening the door to lighter setups and traditional

Web3-style usage. But even then, all security-critical operations will still rely on the same

verifiable, hardened core — giving users a choice between maximum local control and

browser-based convenience.


# How Transactions Work

Before anything can happen on HyperMall — before you can trade or even see balances — you

need to bring in assets. And those assets don’t just come from anywhere: they come from the

TAP Protocol, which is built on top of Bitcoin.

That means every user starts by depositing TAP Protocol tokens into HyperMall. These

tokens are held in the TAP Wallet, and to interact with HyperMall, you simply send them to a

specific deposit address provided by the app. That address is tied to your identity within

HyperMall.

You can deposit any token that exists on the TAP Protocol — including TAP, GIB, USDT,

USDC, and many others. Once those tokens are received, HyperMall recognizes them and

makes them available inside the app.

HyperMall acts like an L2, but at the application level. It’s not a new chain or a rollup. It

doesn’t batch proofs or submit checkpoints. Instead, it bridges assets into a local environment

purpose-built for trading.


# Trading in HyperMall

HyperMall’s trading engine is built on smart contracts — but not in the traditional sense. Rather

than running on a blockchain-based VM, these contracts are executed locally by every

HyperMall node. When you install the HyperMall app, you're not just accessing the exchange —

you’re running the infrastructure that powers it.

Each node includes the same trading smart contract, which defines how orders are created,

matched, and settled. These contracts are deterministic, but because nodes operate

independently, they still need to reach consensus to ensure they all arrive at the same result.

That consensus is achieved by processing a shared stream of validated transactions — ordered

and signed by a separate layer called the Main Settlement Bus (MSB).

The MSB doesn’t execute trades. It validates transactions, checks their structure and

compliance with protocol rules, and signs them. Only transactions signed by the MSB are

accepted by HyperMall nodes’ smart contract for execution.

Participation in the MSB is governed by its own smart contract that defines who can operate as

a validator. Only licensed MSB validators — entities granted permission via this contract —

can sign transactions. In return for maintaining the integrity of the system, validators earn 50%

of all trading fees generated by HyperMall.HyperMall clients can choose which validator to send a transaction to. Validators compete

based on uptime and responsiveness — the better they perform, the more likely they are to earn

validation fees.

Once signed, the transaction is sent to all nodes. Each node executes the smart contract, and

the network reaches consensus through deterministic logic, not mining or proof-of-stake.


# Withdrawing Assets from HyperMall

HyperMall puts users fully in control of their assets — including how they exit the system. When

you’re ready to withdraw, you’re not relying on HyperMall to automatically settle tokens back to

Bitcoin. Instead, the withdrawal process is designed to hand control back to the user through the

TAP Wallet.

After trading inside HyperMall, your TAP tokens (like GIB, USDT, or TAP) are held within your

local app. When you initiate a withdrawal, HyperMall prepares a claim that proves you’re

entitled to receive those tokens back on Bitcoin. This claim is cryptographically signed and tied

to your identity and wallet address.

To complete the withdrawal, you open your TAP Wallet and use it to redeem the claim. The

wallet interacts with the TAP protocol on Bitcoin and finalizes the transfer of your tokens from

the HyperMall layer back to your Bitcoin wallet.


# Fee Structure in HyperMall

HyperMall uses a simple but effective fee model. Here an example for a TAP/USDT pair:

* Makers pay 0.4% in USDT
* Takers pay 0.6% in TAP

Fees are split evenly:

50% goes to the validator who signed the transaction via the MSB

50% goes to the HyperMall protocol and is available to be redistributed via ecosystem

incentives.

Validators are selected by the user or by the app at random. This creates an open validator

economy where performance directly influences rewards. We expect a validator economy to

evolve akin to Bitcoin’s mining economy.


# Supported Token Pairs

HyperMall supports any token pair enabled through governance. At launch, the only supported

pair is: GIB/TAP

Future pairs may include but are not limited to:

* TAP/USDT
* GIB/TAP
* DMT-NAT/USDT
* WBTC/USDT
* ADA/USDT
* GIB/USDT
* WETH/USDT
* TRON/USDT

HyperMall also supports the expansion of the TAP Protocol, including BRC-20 and Runes,

enabling secure trading of a wide variety of Bitcoin-native assets.


# Further Incentives

HyperMall introduces multiple incentive layers:

1. Validator Rewards
   1. Validators receive 50% of trading fees from transactions they sign.
2. Ecosystem Staking Rewards
   1. TAP holders can stake to receive a share of the protocol’s 50% fee pool.
   2. Rewards depend on stake amount and duration.
3. GIB Collectibles
   1. Every staker receives at least one collectible based on Digital Matter Theory from the TAP Protocol. The collection will be introduced shortly after the launch of Hypermall.
4. Future Programs
   1. Liquidity incentives, volume-based rebates, referrals, and governance-based rewards.


# Running a Node

* HyperMall Node (User Level)
  * Every app installation is a full HyperMall node
  * Just install and run — no terminal required
  * Recommended: 100GB of disk space, basic internet, standard office PC
* MSB Validator Node (Operator Level)
  * Requires CLI-based validator software (documentation and support will be available shortly before launch)
  * Recommended:
  * 500GB SSD
  * 8-core CPU with high single-thread performance
  * 16GB RAM
  * High-speed internet (preferably data center hosted)
  * Validators must hold a license to operate


# Conclusion

HyperMall represents a new chapter in decentralized trading — combining the speed and

usability of centralized exchanges with the transparency, control, and programmability of

crypto-native systems.

Built on the Trac Network and powered by the TAP protocol, HyperMall introduces a fast,

headless, and secure trading layer that doesn't compromise on decentralization. With smart

contract-driven execution, validator-driven settlement, and user-controlled asset custody,

HyperMall redefines what it means to trade peer-to-peer — no browser extensions, no bridges,

no friction.

Whether you're a casual trader, a validator, or a builder looking to tap into the future of crypto

infrastructure, HyperMall offers a new kind of platform: native, permissionless, and engineered

for the next wave of real-world adoption.


