IMPORTANT NOTICE: This project is currently in the alpha stage of development and is not intended for use in production environments. The software may contain bugs, incomplete features, or other issues that could cause it to malfunction. Use at your own risk.
We welcome contributions and feedback to help improve this project, but please be aware that the codebase is still under active development. It is recommended to thoroughly test any changes or additions before deploying them in a production environment.
Thank you for your understanding and support!
nuklaivm
takes inspiration from morpheusvm implements the functionality of this VM. In addition, nuklaivm
also adds additional functionality such as token functionalities(fungible, non-fungible and fraction) and staking native token NAI
, has an
emission balancer that keeps track of total supply of NAI, max supply of NAI, staking rewards per block and the emission
address to direct 50% of all fees to.
nuklaivm
is considered ALPHA software and is not safe to use in
production. The framework is under active development and may change
significantly over the coming months as its modules are optimized and
audited.
- ☑ Transfer both the native asset
NAI
and any other token created by users - ☑ Create a token(fungible, non-fungible, dataset)
- ☑ Mint a token(fungible, non-fungible)
- ☑ Burn a token(fungible, non-fungible)
- ☑ Register validator for staking
- ☑ Withdraw validator from staking
- ☑ Delegate NAI to any currently staked validator
- ☑ Undelegate NAI from a staked validator
- ☑ Claim Validator staking rewards
- ☑ Claim User delegation rewards
- ☑ Create dataset
- ☑ Create dataset using an existing token of type dataset
- ☑ Initiate contribution to the dataset
- ☑ Complete contribution to the dataset
- ☑ Publish the dataset to Nuklai marketplace
- ☑ Subscribe to the dataset in the Nuklai marketplace
- ☑ Claim accumulated subscription payment from the Nuklai marketplace
- ☑ Tracks total supply of NAI, max supply of NAI, staking rewards per block and the emission address to direct 50% of all fees to
- ☑ Register validator for staking
- ☑ Unregister validator from staking
- ☑ Delegate
NAI
to a validator - ☑ Undelegate
NAI
from a validator - ☑ Claim the staking/delegation rewards
- ☑ Track the staking information for each users and validators
- ☑ Distribute 50% fees to emission balancer address and 50% to all the staked validators per block
- ☑ Distribute NAI as staking rewards to the validators that have a minimum stake of at least 100 NAI per block
The basis of the nuklaivm
is the ability to create, mint, and transfer user-generated
tokens with ease. When creating an asset, the owner is given "admin control" of
the asset functions and can later mint more of an asset, update its metadata
(during a reveal for example), or transfer/revoke ownership (if rotating their
key or turning over to their community).
Assets are a native feature of the nuklaivm
and the storage engine is
optimized specifically to support their efficient usage (each balance entry
requires only 72 bytes of state = assetID|publicKey=>balance(uint64)
). This
storage format makes it possible to parallelize the execution of any transfers
that don't touch the same accounts. This parallelism will take effect as soon
as it is re-added upstream by the hypersdk
(no action required in the
nuklaivm
).
On nuklaivm
, the emission balancer handles the staking mechanism whereby it tracks the
total supply of NAI
, max supply of NAI
, staking rewards per block and the emission address to direct 50% of all fees to.
Furthermore, it also rewards all the validators that have a minimum stake and all the users who have a minimum delegated stake to a validator of their choice.
Read more about Emission Balancer.
Clone the NuklaiVM repository:
git clone [email protected]:Nuklai/nuklaivm.git
cd nuklaivm
This will clone and checkout the main
branch.
Build NuklaiVM by running the build script:
./scripts/build.sh
The nuklaivm
binary is now in the build/./
directory.
To run the unit tests for NuklaiVM, run the following command:
./scripts/tests.unit.sh
You should see the following output:
github.com/nuklai/nuklaivm/utils coverage: 0.0% of statements
github.com/nuklai/nuklaivm/config coverage: 0.0% of statements
github.com/nuklai/nuklaivm/genesis coverage: 0.0% of statements
github.com/nuklai/nuklaivm/consts coverage: 0.0% of statements
github.com/nuklai/nuklaivm/vm coverage: 0.0% of statements
github.com/nuklai/nuklaivm/dataset coverage: 0.0% of statements
github.com/nuklai/nuklaivm/storage coverage: 0.0% of statements
ok github.com/nuklai/nuklaivm/actions 1.308s coverage: 50.5% of statements
ok github.com/nuklai/nuklaivm/emission 1.306s coverage: 2.1% of statements [no tests to run]
To run the integration tests for NuklaiVM, run the following command:
./scripts/tests.integration.sh
You should see the following output:
[DeferCleanup (Suite)]
/Users/user/go/src/github.com/ava-labs/hypersdk/tests/integration/integration.go:156
[DeferCleanup (Suite)] PASSED [0.000 seconds]
------------------------------
Ran 12 of 12 Specs in 1.120 seconds
SUCCESS! -- 12 Passed | 0 Failed | 0 Pending | 0 Skipped
PASS
coverage: [no statements]
composite coverage: [no statements]
Ginkgo ran 1 suite in 6.748482403s
Test Suite Passed
./scripts/run.sh;
By default, it will store all the subnet files under $HOME/.hypersdk
This also allocates all funds on the network to created address: 00c4cb545f748a28770042f893784ce85b107389004d6a0e0d6d7518eeae1292d9
. The private
key for this address is 323b1d8f4eed5f0da9da93071b034f2dce9d2d22692c172f3cb252a64ddfafd01b057de320297c29ad0c1f589ea216869cf1938d88c9fbd70d6748323dbf2fa7
.
For convenience, this key has is also stored at demo.pk
._
Alternatively, you can also run the network via docker:
./scripts/run_docker.sh start
And you can check out the logs at:
./scripts/run_docker.sh logs
To stop the network, run:
./scripts/stop.sh
Alternatively, with docker:
./scripts/run_docker.sh stop
On default settings, the address 00c4cb545f748a28770042f893784ce85b107389004d6a0e0d6d7518eeae1292d9
will get the entirety of 853 million NAI allocated.
You can run the chain with a different allocation as well:
./scripts/run.sh --initial-owner-address 002b5d019495996310f81c6a26a4dd9eeb9a3f3be1bac0a9294436713aecc84496
or with docker script:
./scripts/run_docker.sh start -initial-owner-address 002b5d019495996310f81c6a26a4dd9eeb9a3f3be1bac0a9294436713aecc84496
Also on default settings, the external subscriber is not run. If you want to connect nuklaivm to an external subscriber, you can do:
./scripts/run.sh --external-subscriber-server-address 127.0.0.1:50051
or with docker script(assuming you're also running external subscriber in docker):
./scripts/run_docker.sh start --external-subscriber-server-address "nuklaivm-subscriber:50051"
# Note that if you are running the subscriber natively(not in docker), you must pass in your docker IP like:
# ./scripts/run_docker.sh start --external-subscriber-server-address 172.0.0.1:50051
# or whatever your docker ip is
For an example external subscriber, you can refer to https://github.com/Nuklai/nuklaivm-external-subscriber.
Note that these flags also work with ./scripts/run_docker.sh
.
The run script uses AvalancheGo's tmpnet to launch a 2 node network with one node's server running at the hardcoded URI: http://127.0.0.1:9650/ext/bc/nuklaivm
.
Each default API comes with its own extension. For example, you can get the networkID
, subnetID
, and chainID
by hitting the /coreapi
extension:
curl -X POST --data '{
"jsonrpc":"2.0",
"id" :1,
"method" :"hypersdk.network",
"params" : {}
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/nuklaivm/coreapi
This should return the following JSON:
{
"jsonrpc": "2.0",
"result": {
"networkId": 88888,
"subnetId": "yfnUMucMT51SgDQRHS5dGZrHKSLzhp49ReUfp9abfHBhQ4XV2",
"chainId": "2P3GpJ3BDN9u8ejJ9tfqvMy5deub1BevNmt2A9qhAcUWskioqW"
},
"id": 1
}
You can also check the balance by doing:
curl -X POST --data '{
"jsonrpc":"2.0",
"id" :1,
"method" :"nuklaivm.balance",
"params" : {"address":"00c4cb545f748a28770042f893784ce85b107389004d6a0e0d6d7518eeae1292d9","asset":"NAI"}
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/nuklaivm/nuklaiapi
This should return the following JSON:
{
"jsonrpc": "2.0",
"result": {
"amount": 853000000000000000
},
"id": 1
}
You can also retrieve info about the latest block by doing:
curl -X POST --data '{
"jsonrpc":"2.0",
"id" :1,
"method" :"indexer.getLatestBlock",
"params" : {}
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/nuklaivm/indexer
This should return the following JSON:
{
"jsonrpc": "2.0",
"result": {
"block": {
"blockID": "2qhX7DnjZDZN6NLHqeAa1kt2rASkEruw6dgGiigTU2uDbhgxq5",
"block": {
"parent": "2JkaGnEor8hGWx7pYq369VLoQgcqRMqd8VwezHjBZL6GUopmS3",
"timestamp": 1731951735240,
"height": 1280,
"txs": [],
"stateRoot": "t3jEXt5QoGLmqNW7wwyKmxX56Rsk3reLVLtnRNTdo2ff5CzMF"
},
"results": [],
"unitPrices": {
"bandwidth": 100,
"compute": 100,
"storageRead": 100,
"storageAllocate": 100,
"storageWrite": 100
}
},
"blockBytes": "00000054ac00a57f169f84a4d65617f9c4998723156257fed54f88487ba9159e8d862a7700000193405e31c800000000000005000000000073e8964327ff6b67681646b2f009b05ce5881c64b8923ba99f13e7885039be28000000040000000000000000000000640000000000000064000000000000006400000000000000640000000000000064"
},
"id": 1
}
Or, if you want to retrieve info about a particular transaction:
curl -X POST --data '{
"jsonrpc":"2.0",
"id" :1,
"method" :"indexer.getTx",
"params" : {"txId": "dCFJa56Nd9SAU33yyjZpQQ4XKjuQNaM5x2WzJSTy2WUK9pTAA"}
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/nuklaivm/indexer
This should return:
{
"jsonrpc": "2.0",
"result": {
"timestamp": 1731951141188,
"success": true,
"units": {
"bandwidth": 223,
"compute": 7,
"storageRead": 45,
"storageAllocate": 135,
"storageWrite": 75
},
"fee": 48500,
"result": ["000bd676fa923171180000000077359400"]
},
"id": 1
}
Note: if you run into any issues starting your network, try running the following commands to troubleshoot and create a GitHub issue to report:
ps aux | grep avalanchego
If this prints out a number of AvalancheGo processes that are still running:
user 18157 18.0 1.4 413704400 466256 ?? Ss Tue08AM 293:30.87 /Users/user/.hypersdk/avalanchego-d729e5c7ef9f008c3e89cd7131148ad3acda2e34/avalanchego --config-file /Users/user/.tmpnet/networks/20240903-083857.802339-nuklaivm-e2e-tests/NodeID-u9eMTbMcPWAj3yer1jhdheJTZL3yvC75/flags.json
user 18164 11.9 1.4 413686944 468480 ?? Ss Tue08AM 262:35.56 /Users/user/.hypersdk/avalanchego-d729e5c7ef9f008c3e89cd7131148ad3acda2e34/avalanchego --config-file /Users/user/.tmpnet/networks/20240903-083857.802339-nuklaivm-e2e-tests/NodeID-KDrJ72L2Uvc2sgxsg22T4CanD2bTdNyqD/flags.json
user 18163 5.8 1.8 414329136 588672 ?? S Tue08AM 59:58.94 /Users/user/.hypersdk/avalanchego-d729e5c7ef9f008c3e89cd7131148ad3acda2e34/plugins/qCNyZHrs3rZX458wPJXPJJypPf6w423A84jnfbdP2TPEmEE9u
user 18174 1.2 1.8 414329120 590544 ?? S Tue08AM 59:20.53 /Users/user/.hypersdk/avalanchego-d729e5c7ef9f008c3e89cd7131148ad3acda2e34/plugins/qCNyZHrs3rZX458wPJXPJJypPf6w423A84jnfbdP2TPEmEE9u
user 33458 1.1 0.2 411703840 76336 ?? Ss Fri05PM 55:42.04 /Users/user/.hypersdk/avalanchego-d729e5c7ef9f008c3e89cd7131148ad3acda2e34/avalanchego --config-file /Users/user/.tmpnet/networks/20240830-171145.374858-nuklaivm-e2e-tests/NodeID-PC24PqjXQFN81PA6a21msrhqtd6Axkvre/flags.json
user 22880 0.0 0.0 410059824 48 s001 R+ 2:42PM 0:00.00 grep avalanchego
The following command will clean up to ensure that you can start the network:
killlall avalanchego
Next, you'll need to add the chains you created and the default key to the
nuklai-cli
. You can use the following commands from this location to do so:
./build/nuklai-cli key import ed25519 demo.pk
If the key is added correctly, you'll see the following log:
database: .nuklai-cli
imported address: 00c4cb545f748a28770042f893784ce85b107389004d6a0e0d6d7518eeae1292d9
Next, you'll need to store the URLs of the nodes running on your Subnet:
./build/nuklai-cli chain import
This will automatically import the uri with the value http://127.0.0.1:9650/ext/bc/nuklaivm
.
If you want to import another chain with its uri, you can do the following:
./build/nuklai-cli chain import "http://127.0.0.1:41177/ext/bc/nuklaivm"
You can confirm the chain was imported correctly by running:
./build/morpheus-cli chain info
This should output something like the following:
available chains: 1
0) chainID: 2F1QmuxSSVntNHXnEevYHBZzyhsNGvAE5Y2pqJW2a4iBugTMWd
select chainID: 0 [auto-selected]
networkID: 88888 subnetID: MBor2t7Ahsr8hn1mW6QCNs5mYXxRnYM3KvkTjrg2m5MeHjB8o chainID: 2F1QmuxSSVntNHXnEevYHBZzyhsNGvAE5Y2pqJW2a4iBugTMWd
To confirm you've done everything correctly up to this point, run the following command to get the current balance of the key you added:
./build/nuklai-cli key balance
If successful, the balance response should look like this:
address: 00c4cb545f748a28770042f893784ce85b107389004d6a0e0d6d7518eeae1292d9
chainID: PAroBtUb83kcU5m3XiD37D263cB8kaZsiZ1rG7DLKxs8EX7Cq
address: 00cf77495ce1bdbf11e5e45463fad5a862cb6cc0a20e00e658c4ac3355dcdc64bb balance: 853000000.000000000 NAI
You can also check the balance of another address by passing in the address as the argument
./build/nuklai-cli key balance 01b27c7ce992cdb7ff039294d7901851902394bb85fa4f3dc4cbb960b07284b7f9
Should give output
chainID: PAroBtUb83kcU5m3XiD37D263cB8kaZsiZ1rG7DLKxs8EX7Cq
address: 00cf77495ce1bdbf11e5e45463fad5a862cb6cc0a20e00e658c4ac3355dcdc64bb balance: 0.000000000 NAI
Now that we have a balance to send, we need to generate another address to send to. Because we use bech32 addresses, we can't just put a random string of characters as the recipient (won't pass checksum test that protects users from sending to off-by-one addresses).
./build/nuklai-cli key generate secp256r1
Note that we are now generating a key with curve secp256r1 instead of ed25519 like our first account. We can do this because the vm supports three types of keys(ed25519, secp256r1 and bls)
If successful, the nuklai-cli
will emit the new address:
created address: 011ddbf62f227dd32deea73b31945d65bb6676cccae6cf0b829dfc21b290387bac
Private Key String(Base64): J5kwBjyvMuvV4PfjTUOO8ZF40Db3KzhFidhH7ER+9Jg=
We can also generate a bls key doing
./build/nuklai-cli key generate bls
By default, the nuklai-cli
sets newly generated addresses to be the default. We run
the following command to set it back to demo.pk
:
./build/nuklai-cli key set
You should see something like this:
chainID: PAroBtUb83kcU5m3XiD37D263cB8kaZsiZ1rG7DLKxs8EX7Cq
stored keys: 3
chainID: PAroBtUb83kcU5m3XiD37D263cB8kaZsiZ1rG7DLKxs8EX7Cq
0) address: 00cf77495ce1bdbf11e5e45463fad5a862cb6cc0a20e00e658c4ac3355dcdc64bb balance: 853000000.000000000 NAI
chainID: PAroBtUb83kcU5m3XiD37D263cB8kaZsiZ1rG7DLKxs8EX7Cq
1) address: 00cf77495ce1bdbf11e5e45463fad5a862cb6cc0a20e00e658c4ac3355dcdc64bb balance: 0.000000000 NAI
chainID: PAroBtUb83kcU5m3XiD37D263cB8kaZsiZ1rG7DLKxs8EX7Cq
2) address: 00cf77495ce1bdbf11e5e45463fad5a862cb6cc0a20e00e658c4ac3355dcdc64bb balance: 0.000000000 NAI
set default key: 0
There may be times when you just want to generate random nuklaivm addresses that have no associated private key. In theory, there could be a private key that corresponds to any given address, but the probability of such an occurrence is extremely low, especially when dealing with a sufficiently large random space.
However, to be certain that an address does not correspond to any private key, we can construct it in such a way that it falls outside the normal range of addresses generated from private keys.
The vanity address generation process involves creating random blockchain addresses and checking if they start with a specific prefix (e.g., "nuklai"). We generate cryptographically secure random IDs and construct addresses using these IDs. To speed up the search, the process runs in parallel across multiple CPU cores, with each worker testing a batch of addresses. The process continues until an address matching the desired prefix is found.
When you do
./build/nuklai-cli key generate-vanity-address nuklai
This will put "kiran" as the prefix and use that to generate a new vanity address.
You should see something like:
Using 24 workers to generate vanity address
Vanity Address: ffeb380a5e8b4ee882081e3f0f9b6378cbdb935245f6bd82df13246dae80e5f056
We are creating an address that includes the word "nuklaivmvanity" followed by random 19 bytes of data. This kind of address is highly unlikely to be generated from a private key because it does not follow the typical structure of addresses derived from private keys.
Lastly, we trigger the transfer:
./build/nuklai-cli action transfer
The nuklai-cli
will emit the following logs when the transfer is successful:
address: 00c4cb545f748a28770042f893784ce85b107389004d6a0e0d6d7518eeae1292d9
chainID: PAroBtUb83kcU5m3XiD37D263cB8kaZsiZ1rG7DLKxs8EX7Cq
assetAddress (use NAI for native token): NAI
address: 00cf77495ce1bdbf11e5e45463fad5a862cb6cc0a20e00e658c4ac3355dcdc64bb balance: 853000000.000000000 NAI
✔ amount: 1█
continue (y/n): y
✅ txID: wjzqXJeYedVSyBWfapoGiHYC9EQr2HgkA7xrVW4KZQyP1jxxG
txID: wjzqXJeYedVSyBWfapoGiHYC9EQr2HgkA7xrVW4KZQyP1jxxG
fee consumed: 0.000048500 NAI
To provide a better sense of what is actually happening on-chain, the
nuklai-cli
comes bundled with a simple explorer that logs all blocks/txs that
occur on-chain. You can run this utility by running the following command from
this location:
./build/nuklai-cli chain watch
If you run it correctly, you'll see the following input (will run until the network shuts down or you exit):
available chains: 1
0) chainID: 2F1QmuxSSVntNHXnEevYHBZzyhsNGvAE5Y2pqJW2a4iBugTMWd
select chainID: 0 [auto-selected]
uri: http://127.0.0.1:9650/ext/bc/nuklaivm
watching for new blocks on 2F1QmuxSSVntNHXnEevYHBZzyhsNGvAE5Y2pqJW2a4iBugTMWd 👀
height:3003 txs:1 root:uNXBoJRGNo8JCJ8XDEioqVnJjwrVSSKBMgbaTd9AWFiUke2vE size:0.30KB units consumed: [bandwidth=224 compute=7 storage(read)=14 storage(allocate)=50 storage(write)=26] unit prices: [bandwidth=100 compute=100 storage(read)=100 storage(allocate)=100 storage(write)=100] [TPS:0.10 latency:66ms gap:142ms]
✅ 2deLZJJpfXm1Wrad1f8uZL1aZnrCkEtBZ6aFXNcR7stFYN8Rm8 actor: 00c4cb545f748a28770042f893784ce85b107389004d6a0e0d6d7518eeae1292d9 summary (*actions.Transfer): [assetID: 00cf77495ce1bdbf11e5e45463fad5a862cb6cc0a20e00e658c4ac3355dcdc64bb amount: 1000000000 -> 00cf77495ce1bdbf11e5e45463fad5a862cb6cc0a20e00e658c4ac3355dcdc64bb
]
Refer to Emission Balancer Demo to learn how to retrieve info such as totalsupply, rewardsperblock, staking info, etc from Emission Balancer.
Refer to Assets Demo to learn how to mint an asset, transfer it within the same subnet or to another subnet with AWM, etc.
Refer to Datasets Demo to learn how to create a dataset, update it, add data to dataset, etc.
Refer to Marketplace Demo to learn how to create a publish your dataset up for sale on the Nuklai Marketplace and how to subscribe to a dataset.
Refer to WASM Smart Contracts Demo to learn how to deploy smart contracts and interact with them on NuklaiVM.
You can run the faucet by doing:
FAUCET_PRIVATE_KEY_HEX="323b1d8f4eed5f0da9da93071b034f2dce9d2d22692c172f3cb252a64ddfafd01b057de320297c29ad0c1f589ea216869cf1938d88c9fbd70d6748323dbf2fa7" RPC_ENDPOINT="http://127.0.0.1:9650" go run ./cmd/faucet
You can run the web wallet that provides access to the Metamask Snap and uses the VM's ABI to let users interact with any of the actions defined such as transfer, create_asset, create_dataset, etc.
cd web_wallet;
yarn;
yarn dev;