Alephium

This wiki contains the documentation of Alephium, a sharded blockchain that makes programmable money scalable and secure.

Overview

The protocol's innovations extend battle-tested ideas from Bitcoin and Ethereum:

  • BlockFlow algorithm based on UTXO model enables sharding and scalability for today (code + algorithm paper)
    • The first sharding algorithm that supports single-step cross-shard transactions, offering the same user experience as single chain
    • Simple and elegant PoW based sharding, does not rely on beacon chain
  • Stateful UTXO model combines the advantages of both eUTXO model and account model (see code, wiki to come)
    • Tokens are first-class citizens and UTXO-based, which are owned by users directly instead of contracts
    • Offer the same expressiveness as account model. DApps can be easily built on top of it with better security
    • Support multiple participants in a single smart contract transaction. Multiple calls can be packed into a single transaction too.
  • Novel VM design resolves many critical challenges of dApp platforms (see code, wiki to come)
    • Less IO intensive
    • Flash loan is not available by design
    • Eliminate many attack vectors of EVM, including unlimited authorization, double dip issue, reentrancy attack, etc
    • UTXO style fine-grained execution model reduces risk-free arbitrage
  • Front-running mitigation through random execution of transactions (see code, wiki to come)
  • PoLW algorithm reduces the energy consumption of PoW in the long term (research paper)
    • Adaptive rewards based on hashrate and timestamp are designed and implemented
    • Internal mining cost through burning will be added when hashrate and energy consumption is significantly high

Starter Guide: How to Launch your Node

Block explorer: https://explorer.alephium.org

Requirements

Ensure that Java is installed on your computer:

Download Application File

Download file alephium-1.1.6.jar from Github release (do not double click on it, it can not be launched this way).

Start your node

  1. Open the search and type in Terminal (for Mac and Ubuntu) or Command Prompt (for Windows).
  2. In the Terminal/Command Prompt program, type cd your-jar-file-path to enter the folder in which the alephium-1.1.6.jar file is saved.
  3. Type the following command in the terminal and press Enter to launch the full node:
    java -jar -Xmx500m alephium-1.1.6.jar
    

🎉 Tada, your node is running

  • Your node will start to sync with the network. It might take long the first time. Your node has been fully synced once the block height in the terminal logs is equal to the one found in the latest blocks of the explorer.
  • If you close the terminal the node will be turned off.
  • All of the blockchain data is stored in .alephium under your home folder1.

Swagger

We use OpenAPI to interact with the full node. You can directly open Swagger UI through http://127.0.0.1:12973/docs.

Alternatively, you can use any OpenAPI client to import the openapi.json file from our repository (download).

Mining

For mining, you can follow our GPU Miner Guide.

Wallet

You could download the desktop wallet from here: https://github.com/alephium/alephium-wallet/releases/tag/v1.0.2

Alternatively, our full node has a builtin wallet with advanced features, you can follow our Wallet Guide to learn how to use it.

1

The home folder depends on your system: C:\Users\<your-username> in Windows, /Users/<your-username> in macOS, /home/<your-username> in Linux.

Docker guide

You can find the instructions on how run a full node with docker-compose in 1 minute here: https://github.com/alephium/alephium/tree/master/docker

Running a dockeri'zed full node on a Raspberry Pi 4

In this guide we'll learn:

  • How to install a Raspberry Pi 4
  • How to run a docker'ized instance of Alephium full node

How to install a Raspberry Pi 4

This first section will detail my personal way of installing Ubuntu 20.04 server on a Raspberry Pi 4. It requires to have a Raspberry Pi 4 (obviously), a SD Card (8 GB is the minimum) and an SD Card reader to flash the SD Card. It will be illustrated using shell command from macOS, but you'll find the equivalent in Windows.

Getting ready for the hard work

First of all we will configure the installation Ubuntu. We're using cloud-init for that since it is built in Ubuntu 20.04 and above. This configuration creates a user (different from the ubuntu default) and installs a few packages.

Configure the boot

Put the snippet below in a file named user-data.yml and save it. This one creates a user alephium with the password installfest2021. You can customize the content of this file if you know what you're doing.

#cloud-config

hostname: alephium

ssh_pwauth: true

users:
  - name: alephium
    gecos: "alephium"
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash
    groups: adm,sudo,docker
    plain_text_passwd: installfest2021
    lock_passwd: false
    chpasswd: { expire: false }
#    ssh_authorized_keys: # Optionally ad ssh key here, if you don't want the password.
#      - ssh-ed25519 xxxxxxxxxxxxxxxxxx

packages:
  - apt-transport-https
  - ca-certificates
  - curl
  - gnupg-agent
  - software-properties-common
  - git
  - openssh-server
  - docker.io
  - docker-compose

runcmd:
  - systemctl start docker
  - systemctl enable docker

package_update: true
package_upgrade: true

power_state:
  mode: reboot

Flash the SD Card

Now, we'll flash the SD Card including this file user-data.yml.

I'm using the tool flash for this, which does most of the hard work for you.

curl -LO https://github.com/hypriot/flash/releases/download/2.7.2/flash
chmod +x flash

./flash --userdata user-data.yml https://cdimage.ubuntu.com/releases/20.04/release/ubuntu-20.04.3-preinstalled-server-arm64+raspi.img.xz

The command above will ask for confirmation that /dev/disk2 is the SD Card and not your harddrive, and will ask your password because flashing a SD Card requires admin privileges.

Once the command above completes, you can insert the SD Card in your Raspberry Pi and turn it on. It takes a handful of minutes for the first boot to execute fully, and your Raspberry Pi is ready to be used. Once the node is ready, you can ssh into it using alephium as username, and installfest2021 as password!

ssh alephium@alephium

If alephium host is unknown, you'll have to search for the IP address of the node, most likely on your router configuration app/page.

And that's it, your Raspberry Pi is running Ubuntu 20.04 with Docker, and is ready to run an Alephium full node.

🚀

Raspberry pi 4

How to run a docker'ized instance of Alephium full node

This second section is not specific to a Raspberry Pi, but can be generalized to any server/vm/computer with SSH access. We will run the most basic version of a Alephium full node using docker, and then iterate to make our setup more convenient to work with.

As a pre-requisite of this section, we must have a server with SSH access, and more precisely running Ubuntu 20.04 or more recent. The previous section explains how to do that with a Raspberry Pi, but an AWS EC2 instance would also do the job.

Connect to the server

This should be an easy step, using the ssh command. Run:

ssh alephium@alephium

Installing docker and docker-compose

Let's install docker and docker-compose quickly, so that we'll be all set to run the Alephium full node.

Once ssh'ed, run the following commands:

sudo apt install -y docker.io docker-compose

Great, docker should be running:

docker ps

Run the full node

Now we can run the full node, in a single line, as follow:

docker run -it --rm -p 12973:12973 --name alephium alephium/alephium:v1.1.6

Docker-compose

Docker-compose is a bit more convenient way of running a container, especially if the command starts to contain volumes, more ports, environment variables, etc...

So, below is the service definition you can put in a docker-compose.yml file, and simply call docker-compose up -d to start your full node from this definition.

version: "3"
services:
  broker:
    image: "alephium/alephium:v1.1.6"
    restart: unless-stopped
    ports:
      - 9973:9973/tcp
      - 9973:9973/udp
      - 10973:10973/tcp
      - 12973:12973/tcp

GPU Miner Guide

You must first follow the steps in the Full Node Starter Guide in order to download, start your node and use Swagger http://127.0.0.1:12973/docs.

Mining Information

  • 4 address groups and 16 chains in total
  • the target block time is 64 seconds
  • everyday, 24 * 60 * 60 / 64 * 16 = 21600 blocks are mined on average
  • the block rewards are 3 ALPH right now
  • all of the mined coins are locked for 500 minutes

For more information about mining rewards, please read this article Block Rewards.

You could get the estimated network hashrate from the log of your full node, or from the Grafana dashboard of the full node if you run it with docker-compose.

Create Miner Wallet

First, you must create a dedicated wallet for mining. As opposed to a traditional wallet, a miner wallet has multiple addresses which are used to collect mining rewards for each address group.

Create your miner wallet

miner-wallet-create-query

The server will return you the new wallet mnemonic. Please backup and store it securely.

miner-wallet-create-response

List your miner addresses

miner-wallet-list-addresses-query

The server will return you 4 addresses for the next step:

miner-wallet-list-addresses-response

Configure Miner Addresses

Now that you have gotten your 4 miner addresses, you must assign it to your node so you can earn rewards when it starts mining. This can be done by adding the following content in the file .alephium/user.conf under your home folder1:

alephium.network.external-address = "x.x.x.x:9973" // put your public IP here; otherwise remove this line
alephium.mining.miner-addresses = [
  "1HiYeRbypJQK4nc6EFYWiRVdsdYukQKq8SvKQsfJ3wiR8",
  "1HD3q1G7qVoeyNA4U6HbBhFvv1FLUWNGwNavPamScpVLa",
  "1CQiD2RQ58ymszcgPEszRomyMZxEjH1Rtp4tB84JY2qgL",
  "19vvD3QbfEYbJexk6yCtnDNpRrfr3xQv2Pzc6x265MRhD"
]

Please restart your node to make these new configs take effect. Please be sure to add them in the same order they were returned by the endpoint, as they are sorted according to their group.

Start Mining

The full node needs to be fully synced to the Alephium network before you could start mining. You could verify that by executing this endpoint:

full-node-synced-query

Nvidia GPU

Please follow the instructions on https://github.com/alephium/gpu-miner to run the gpu miner for Nvidia GPUs.

Alternatively, you could run the gpu-miner with docker by following the documents here https://github.com/alephium/alephium/tree/master/docker#gpu-miner-optional

AMD GPU

Please follow the instructions on https://github.com/alephium/amd-miner to run the gpu miner for AMD GPUs. Note that the performance of AMD miner is not in par with Nvidia miner.

If you have any questions, feel free to reach out to the developers on Discord.

Miner Wallet More

Here are more endpoints that are useful for miners.

Get your balance

miner-wallet-balance-query

Change your active address

miner-wallet-change-active-address

Transfer all your funds on the active address to another address

miner-wallet-sweep-all-query

Unlock your wallet

miner-wallet-unlock-query

1

The home folder depends on your system: C:\Users\<your-username> in Windows, /Users/<your-username> in macOS, /home/<your-username> in Linux.

Mining companion for your Alephium full node

Mining is the activity of securing the blockchain providing compute power, while getting mining block rewards in counterpart. Every block you mine has a reward attached to it, and this reward is sent to the mining wallet that has successfully mined the block.

On the Alephium blockchain, a mining wallet has multiple addresses referring to it, precisely one address per chain in the blockchain. Alephium mainnet has currently 4 groups, so a mining wallet for the mainnet has 4 addresses.

So when you're mining on Alephium blockchain, you'll soon end up having ALPH spread on all the addresses of your mining wallet.

So if you want to centralize the mining block rewards to a normal wallet (with a single address), the mining companion will help you automating this task!

The mining companion

The mining companion is a lightweight, standalone process which, in a nutshell, will connect to a Alephium full node to transfer the block rewards from all the addresses of your mining wallet to a normal, single address wallet. This transfer operation happens at a regular frequency, for instance every 15 minutes. At the trigger time, if the available (unlocked) amount is bigger than a certain threshold, say 20 ALPH, this amount is transferred to your normal wallet. So block rewards are grouped together to limit transaction fee.

Configuration

So let's get started and run a mining companion.

The best way to run the mining companion is via Docker and docker-compose. This guide assumes you have docker and docker-compose already installed on your computer. Please refer to the official documentation to install these two technologies otherwise. You might even have your [Alephium full node running in a docker container](TODO LINK), but this step is not mandatory.

In a docker-compose.yml file, define the mining-companion service as the following:

version: "3"
services:
  mining-companion:
    image: touilleio/alephium-mining-companion:v4
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    environment:
      - ALEPHIUM_ENDPOINT=http://broker:12973
      - TRANSFER_ADDRESS=YOUR WALLET ADDRESS HERE!!
    labels:
      - org.label-schema.group=alephium

In the snippet above, replace YOUR WALLET ADDRESS HERE!! by the address of you normal wallet, for instance 14FGvG61tqzXXYi6UKtzjozMjxCArF1beoU4ogUqM2pAC and make sure ALEPHIUM_ENDPOINT points to your Alephium full node endpoint. Using http://localhost:12973 here is always wrong!

This is it, all the other default configuration options are safe to run. All the configuration options are listed in the [README file of the project source code](https://github.com/touilleio/alephium-mining-companion.

You can then simply start the process, using docker-compose up -d mining-companion, and it will start transferring the block rewards to the address you put in the TRANSFER_ADDRESS variable.

DISCLAIMER: If you put a wrong address in TRANSFER_ADDRESS, your ALPH rewards cannot be used anymore (or at least not by you)!

So double check this address to avoid any mistake.

Recommendations

It is recommended to run one mining companion per Alephium full node, so that the mining wallet password stays local to the full node.

Wallet

The wallet API can be called using our Swagger UI at http://127.0.0.1:12973/docs or by using curl. Make sure that your full node is running so you could access the Swagger UI.

Create a new wallet

You can create a new wallet by doing a POST with the following data on /wallets.

{
  "password": "123456",
  "walletName": "foo" //optional (wallet-x) by default
}

The server must response successfully giving you our new wallet mnemonic.

{
  "walletName": "foo",
  "mnemonic": "laptop tattoo torch range exclude fuel bike menu just churn then busy century select cactus across other merge vivid alarm asset genius mountain transfer"
}

Fetch your new wallet address by GET /wallets/{wallet_name}/addresses

{
  "activeAddress": "T1J2yrmQrNwuFW8z2W6xXFLtJoBCWEm7gLg9BuY8tzKjxw",
  "addresses": ["T1J2yrmQrNwuFW8z2W6xXFLtJoBCWEm7gLg9BuY8tzKjxw"]
}

If you already created a wallet once but it got deleted or you don't remember your password, you can restore your wallet with your mnemonic using:

PUT /wallets
{
    "password": "123456",
    "mnemonic": "laptop tattoo torch range exclude fuel bike menu just churn then busy century select cactus across other merge vivid alarm asset genius mountain transfer",
    "walletName": "foo" //optional
}

Lock/Unlock

You wallet will automatically be locked after some time, you'll need to unlock it if you want to use it:

POST /wallets/{wallet_name}/unlock
{
    "password": "123456"
}

You can also manually lock it:

POST /wallets/{wallet_name}/lock

Query for balance

You can check the current balance with GET /wallets/{wallet_name}/balances response:

{
  "totalBalance": 0,
  "balances": [
    {
      "address": "T1J2yrmQrNwuFW8z2W6xXFLtJoBCWEm7gLg9BuY8tzKjxw",
      "balance": 0
    }
  ]
}

Transfering funds

You can submit a transaction from a wallet to an address by doing:

POST /wallets/{wallet_name}/transfer
{
    "destinations ": [{
        "address": "<the destination address>",
        "amount ": "42 ALPH"
    }]
}

The server must response succussfully with the transaction id and the group information.

{
  "txId": "50318e5bfd56796690890f4a9c5aae2725629a15a71cad909bbf4a669c32c2f4",
  "fromGroup": 0,
  "toGroup": 3
}

Multisig Guide

Alephium is supporting m-of-n multi-signature addresses.

You can find the related command for multisig at http://127.0.0.1:12973/docs under the Multi-signature section. Make sure that your full node is running so that you can access the Swagger UI.

Create a multisig address

  1. Get all the public keys of the accounts for that multisig.

    Public key can be retrieve with the wallet by calling:

    GET /wallets/{wallet_name}/addresses/{address}
    

    response:

    {
      "address": "{address}",
      "publicKey": "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28"
    }
    
  2. For example, if you want to create a multisig address with 3 accounts that needs 2 signatures to unlock (2-of-3), you can do:

    POST /multisig/address
    {
      "keys": [
        "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28",
        "8c1842cb159ac5ec04f384fe2d6f5da2d1b70d2226308b46da297486adb6b41a8f",
        "e4a8c1842cb159ac5ec0b70d2226308b46da297486adb6b4f14f384fe2d6f5da31"
      ],
      "mrequired": 2
    }
    

    response:

    {
      "address": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y9n"
    }
    

    ⚠️ WARNING: Make sure to remember the order of the public keys, you'll need to provide the same order later.

    Funds can now be send to that address.

  3. To use the funds, you need to build a multisig transaction.
    Pass the public keys that will sign the transaction, 2 in our example.
    Make sure to have the same order as during the address creation:

    POST /multisig/build
    {
      "fromAddress": "1AujpupFP4KWeZvqA7itsHY9cLJmx4qTzojVZrg8W9y9n",
      "fromPublicKeys": [
        "d1b70d2226308b46da297486adb6b4f1a8c1842cb159ac5ec04f384fe2d6f5da28",
        "e4a8c1842cb159ac5ec0b70d2226308b46da297486adb6b4f14f384fe2d6f5da31"
      ],
      "destinations": [
        {
          "address": "1jVZrg8W9y9AujpupFP4KWeZvqA7itsHY9cLJmTonx4zq",
          "amount": "42 ALPH"
        }
      ]
    }
    

    response:

    {
      "unsignedTx": "0ecd20654c2e2be708495853e8da35c664247040c00bd10b9b13",
      "txId": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef",
      "fromGroup": 2,
      "toGroup": 1
    }
    
  4. You can now send the txId to the people that need to sign the transaction. Everyone can sign it using their wallet:

    POST /wallets/{wallet_name}/sign
    {
      "data": "798e9e137aec7c2d59d9655b4ffa640f301f628bf7c365083bb255f6aa5f89ef"
    }
    

    response:

    {
      "signature": "9e1a35b2931bd04e6780d01c36e3e5337941aa80f173cfe4f4e249c44ab135272b834c1a639db9c89d673a8a30524042b0469672ca845458a5a0cf2cad53221b"
    }
    
  5. Collect the signatures, 2 in our example (because m=2) and finally send the transaction:

    NOTE: The signatures order needs to be the same as the public keys.

    POST /multisig/submit
    {
       "unsignedTx": "0ecd20654c2e2be708495853e8da35c664247040c00bd10b9b13",
       "signatures": [
       "9e1a35b2931bd04e6780d01c36e3e5337941aa80f173cfe4f4e249c44ab135272b834c1a639db9c89d673a8a30524042b0469672ca845458a5a0cf2cad53221b",
       "ab135272b834c1a639db9c89d673a8a30524042b0469672ca845458a5a0cf2cad53221b9e1a35b2931bd04e6780d01c36e3e5337941aa80f173cfe4f4e249c44"
       ]
    }
    
    

    response:

    {
      "txId": "503bfb16230888af4924aa8f8250d7d348b862e267d75d3147f1998050b6da69",
      "fromGroup": 2,
      "toGroup": 1
    }
    

Smart Contract Guide

This document guides you through the creation, deployment and usage of smart contracts on Alephium mainnet.

We will first deploy a contract which allows any user to exchange ALPH for tokens. Then we will deploy a script which calls the contract to buy tokens.

This document is based on the Chinese smart contract tutorial and documentation by Lbqds.

Table of Contents

Requirements

For this tutorial you will need to have a node running locally and a wallet. Here are the related tutorials:

We will use a wallet named demo-1 in this tutorial.

Create and deploy a token contract

In this section we will create, build, sign and submit a contract transaction.

Create a token Contract

First, we create a token contract as shown below:


#![allow(unused)]
fn main() {
TxContract MyToken(owner: Address, mut remain: U256) {
  pub payable fn buy(from: Address, alphAmount: U256) -> () {
    let tokenAmount = alphAmount * 1000
    assert!(remain >= tokenAmount)
    let tokenId = selfTokenId!()
    transferAlf!(from, owner, alphAmount)
    transferTokenFromSelf!(from, tokenId, tokenAmount)
    remain = remain - tokenAmount
  }
}
}

This simple token contract allows users to buy tokens by paying ALPH to the contract owner at a rate 1:1000. It uses the following built-in functions:

  • assert!(pred) Causes the contract execution to fail when pred evaluates to false
  • selfTokenId!(a) Returns the current token id which is also the current contract id
  • transferAlf!(from, to, alphAmount) Transfers alphAmount ALPH from address from to to.
  • transferTokenFromSelf!(to, tokenId, tokenAmount) Transfers tokenAmount tokens of MyToken to address to.

Note: The remain variable is not necessary but helps understanding state variables of the contract. We will explain how the contract state is stored later.

Compile a Contract

Next we compile the contract via the full node API.

curl -X 'POST' \
  'http://127.0.0.1:12973/contracts/compile-contract' \
  -H 'Content-Type: application/json' \
  -d '{
  "code": "TxContract MyToken(owner: Address, mut remain: U256) {\n  pub payable fn buy(from: Address, alphAmount: U256) -> () {\n    let tokenAmount = alphAmount * 1000\n    assert!(remain >= tokenAmount)\n    let tokenId = selfTokenId!()\n    transferAlf!(from, owner, alphAmount)\n    transferTokenFromSelf!(from, tokenId, tokenAmount)\n    remain = remain - tokenAmount\n  }\n}"
}'

We receive the binary code of the contract as a response:

{
  "code": "0201402c01010204001616011343e82c1702a0011602344db117031600a0001601a7160016031602aba00116022ba101"
}

Build an unsigned contract transaction

Now we need to create the contract transaction. First we obtain the publicKey of the address currently in use. We use address 1Bw9NuSufuvi1EgWFe9uCQS3xi1gkZ81mtdPRhPbSqw5r.

curl 'http://127.0.0.1:12973/wallets/demo-1/addresses/1Bw9NuSufuvi1EgWFe9uCQS3xi1gkZ81mtdPRhPbSqw5r'

We obtain the following response:

{
  "address": "1Bw9NuSufuvi1EgWFe9uCQS3xi1gkZ81mtdPRhPbSqw5r",
  "publicKey": "027b029462e7df54cd218cc46e2f3c8fd6cfd3c0d475474e5a3ded70d809df04a7"
}

Then we build the contract transaction.

curl -X 'POST' \
  'http://127.0.0.1:12973/contracts/build-contract' \
  -H 'Content-Type: application/json' \
  -d '{
  "fromPublicKey": "027b029462e7df54cd218cc46e2f3c8fd6cfd3c0d475474e5a3ded70d809df04a7",
  "code": "0201402c01010204001616011343e82c1702a0011602344db117031600a0001601a7160016031602aba00116022ba101",
  "gas": 80000,
  "state": "[@1Bw9NuSufuvi1EgWFe9uCQS3xi1gkZ81mtdPRhPbSqw5r,10000000000000000000000000000u]",
"issueTokenAmount": "10000000000000000000000000000"
}'

The parameters are:

  • fromPublicKey Public key from the address currently in use
  • code Contract binary code
  • gas Manually specified gas as the default gas may not be enough for contract related operations
  • state List of initial state variables passed to the contract constructor
  • issueTokenAmount The total number of tokens issued by the contract

We get the following response:

{
  "unsignedTx": "0101010101000000071500a273b4a26cef6181b90a80c5e8738478ed35b9ea10f9e00e12225174a86ebd8113c1e8d4a51000a21440300201402c01010204001616011343e82c1702a0011602344db117031600a0001601a7160016031602aba00116022ba101144031020400a273b4a26cef6181b90a80c5e8738478ed35b9ea10f9e00e12225174a86ebd8102c8204fce5e3e2502611000000013c8204fce5e3e25026110000000ae80013880c1174876e8000145827e150d3aa8643039f353c4e0f6bc554d53e61304f566f3302323c89eede0b86e130d00027b029462e7df54cd218cc46e2f3c8fd6cfd3c0d475474e5a3ded70d809df04a700",
  "hash": "8d01198f2ec74b1e5cfd8c8a37d6542d16ee692df47700ce2293e0a22b6d4c22",
  "contractId": "109b05391a240a0d21671720f62fe39138aaca562676053900b348a51e11ba25",
  "fromGroup": 0,
  "toGroup": 0
}

Sign a contract

Next, we sign the previously obtained transaction hash.

curl -X 'POST' \
  'http://127.0.0.1:12973/wallets/demo-1/sign' \
  -H 'Content-Type: application/json' \
  -d '{
  "data": "8d01198f2ec74b1e5cfd8c8a37d6542d16ee692df47700ce2293e0a22b6d4c22"
}'

The response contains the signature.

{
  "signature": "6bc932a4c9c8c4e8a38fe7f93bd5d43bfe19a3aea2f9d976376951ec7499f5b472e299a12b52e459d66ef13a5aa9de195b2fa97d461e9853eae2281dda4d3cba"
}

Submit a contract

Finally we submit the contract transaction to the Alephium network.

curl -X 'POST' \
  'http://127.0.0.1:12973/transactions/submit' \
  -H 'Content-Type: application/json' \
  -d '{
  "unsignedTx": "0101010101000000071500a273b4a26cef6181b90a80c5e8738478ed35b9ea10f9e00e12225174a86ebd8113c1e8d4a51000a21440300201402c01010204001616011343e82c1702a0011602344db117031600a0001601a7160016031602aba00116022ba101144031020400a273b4a26cef6181b90a80c5e8738478ed35b9ea10f9e00e12225174a86ebd8102c8204fce5e3e2502611000000013c8204fce5e3e25026110000000ae80013880c1174876e8000145827e150d3aa8643039f353c4e0f6bc554d53e61304f566f3302323c89eede0b86e130d00027b029462e7df54cd218cc46e2f3c8fd6cfd3c0d475474e5a3ded70d809df04a700",
  "signature": "6bc932a4c9c8c4e8a38fe7f93bd5d43bfe19a3aea2f9d976376951ec7499f5b472e299a12b52e459d66ef13a5aa9de195b2fa97d461e9853eae2281dda4d3cba"
}'

If the request is valid, a response similar to the following is returned.

{
  "txId": "8d01198f2ec74b1e5cfd8c8a37d6542d16ee692df47700ce2293e0a22b6d4c22",
  "fromGroup": 0,
  "toGroup": 0
}

In order to understand the contract creation process more clearly, let's take a look at the specific content of the above tx. Currently there is no friendly interface to examine the content of a transaction so we need fetch the block containing the transaction via the node API. (You can obtain the block hash of a transaction either via the explorer or the endpoint GET /transaction/status?txId={txId})

curl 'http://127.0.0.1:12973/blockflow/blocks/000000eb4a6e3d3327ef1deeb3f2bc49d4b4af637c749a49bea2a0bfd63d6590' \

We obtain a list of transactions (here we only show 2 out of 5 transactions for readability):

{
  "hash": "000000eb4a6e3d3327ef1deeb3f2bc49d4b4af637c749a49bea2a0bfd63d6590",
  "timestamp": 1633096699956,
  "chainFrom": 0,
  "chainTo": 0,
  "height": 24945,
  "deps": [
    "0000022d620b58c4852640bf497adb353bdddc5238c3d1ee690f2e8fb88d6e75",
    "0000028c0e316c239d44a0f86fd73d57f536e296d73cacea03bb837d6add9bda",
    "00000072e06ddcdf9268e7d4551ac4ca5d8de2467fcd15f2ff11f7a3ded87c7f",
    "000002b60cbf367c591c8b6de3775d3ea042622eefa09ac25e5487e49e331400",
    "000001d7fc80fa66e4ef0a73f54400ae622f8feb27679a3f77f71b35f6477bb1",
    "000000d2921c3d03228f28ef2ecb49d687913e346af2dec63d89c33edc757472",
    "000001a2f388e1a9dbb41e5475031cd1f3a506bb0703fa7818debc624f39f953"
  ],
  "transactions": [
    {
      "id": "1cb33fd8ff18504c6b0432dfeac03ffd050226e62f8c1b14213d5e7bd2533786",
      "inputs": [
        {
          "type": "asset",
          "outputRef": {
            "hint": 1472622741,
            "key": "c89da3210c5f709cd9363f6c12c3fdd1ac7da9f063c05ac9bb0d0fbd5698ba17"
          },
          "unlockScript": "0003dc84c5228297efb1d02c95ca9ec6091c065d4c8a3695ecce9e2c0dd0d04fafbd"
        }
      ],
      "outputs": [
        {
          "type": "asset",
          "amount": "3440997000000000",
          "address": "1BBEMJQpRzFcKsQgfnVH2KhLgP5haD1o7D8dTSQ9QJS1q",
          "tokens": [],
          "lockTime": 0,
          "additionalData": ""
        },
        {
          "type": "asset",
          "amount": "1700253026000000000",
          "address": "125hGgrju9sgDsxd5kkdVm1D6aaGqXRXwcDZjk1WE9DdY",
          "tokens": [],
          "lockTime": 0,
          "additionalData": ""
        }
      ],
      "gasAmount": 20000,
      "gasPrice": "100000000000"
    },
    ,
    {
      "id": "8d01198f2ec74b1e5cfd8c8a37d6542d16ee692df47700ce2293e0a22b6d4c22",
      "inputs": [
        {
          "type": "asset",
          "outputRef": {
            "hint": 1166179861,
            "key": "0d3aa8643039f353c4e0f6bc554d53e61304f566f3302323c89eede0b86e130d"
          },
          "unlockScript": "00027b029462e7df54cd218cc46e2f3c8fd6cfd3c0d475474e5a3ded70d809df04a7"
        }
      ],
      "outputs": [
        {
          "type": "contract",
          "amount": "1000000000000",
          "address": "uomjgUz6D4tLejTkQtbNJMY8apAjTm1bgQf7em1wDV7S",
          "tokens": [
            {
              "id": "109b05391a240a0d21671720f62fe39138aaca562676053900b348a51e11ba25",
              "amount": "10000000000000000000000000000"
            }
          ]
        },
        {
          "type": "asset",
          "amount": "3991999000000000000",
          "address": "1Bw9NuSufuvi1EgWFe9uCQS3xi1gkZ81mtdPRhPbSqw5r",
          "tokens": [],
          "lockTime": 0,
          "additionalData": ""
        }
      ],
      "gasAmount": 80000,
      "gasPrice": "100000000000"
    },
  ]
}

We only focus on the transaction with id 8d01198f2ec74b1e5cfd8c8a37d6542d16ee692df47700ce2293e0a22b6d4c22.

{
  "id": "8d01198f2ec74b1e5cfd8c8a37d6542d16ee692df47700ce2293e0a22b6d4c22",
  "inputs": [
    {
      "type": "asset",
      "outputRef": {
        "hint": 1166179861,
        "key": "0d3aa8643039f353c4e0f6bc554d53e61304f566f3302323c89eede0b86e130d"
      },
      "unlockScript": "00027b029462e7df54cd218cc46e2f3c8fd6cfd3c0d475474e5a3ded70d809df04a7"
    }
  ],
  "outputs": [
    {
      "type": "contract",
      "amount": "1000000000000",
      "address": "uomjgUz6D4tLejTkQtbNJMY8apAjTm1bgQf7em1wDV7S",
      "tokens": [
        {
          "id": "109b05391a240a0d21671720f62fe39138aaca562676053900b348a51e11ba25",
          "amount": "10000000000000000000000000000"
        }
      ]
    },
    {
      "type": "asset",
      "amount": "3991999000000000000",
      "address": "1Bw9NuSufuvi1EgWFe9uCQS3xi1gkZ81mtdPRhPbSqw5r",
      "tokens": [],
      "lockTime": 0,
      "additionalData": ""
    }
  ],
  "gasAmount": 80000,
  "gasPrice": "100000000000"
}

This transaction has one input and two outputs. Below is the description of the some fields:

  • outputRef pointer to UTXO
  • outputRef.key UTXO hash
  • type the type of the tx output, contract or asset
  • address base58 encoding of the contract or asset address
  • amount the amount of ALPH owned by the address
  • tokens list of tokens owned by the address
  • tokens.id contract id
  • tokens.amount the amount of tokens owned

The first output is the contract we just created. We see that the contract address owns 10000000000000000000000000000 tokens which is exactly the issueTokenAmount we previously defined.

The second output is the UTXO output of the transaction submitted by our address 1Bw9NuSufuvi1EgWFe9uCQS3xi1gkZ81mtdPRhPbSqw5r. This address doesn't own any token.

Create and deploy a script

Now that the contract has been successfully created, we will deploy a TxScript which calls the Mytoken.buy method to obtain tokens by paying ALPH to the contract. For this example, we will pay using address 1ELgp7U4D1QL82G9q9dAdp43k45onPDezGLjHSFGcwCj9 which is different than the one used to create the contract.

If you also want to pay with an address different than the one used to submit the contract, please make sure that your address belongs to the same group as the contract. You can obtain the contract group by checking the chainFrom field of its transaction block. In our example, the contract is in group 0, but it might be in a different group for you. You can verify the group of an address at endpoint GET addresses/{address}/group. If it is not the case, you can use POST/wallets/{wallet_name}/derive-next-address until you obtain an address in the correct group. As new addresses are initialized with balance 0, you should transfer some ALPH to this new address. Finally, change your active address at endpoint POST wallets/{wallet_name}/change-active-address.

Create a TxScript

We first create the TxScript to buy some tokens.

TxScript Main {
  pub payable fn main() -> () {
    approveAlf!(@1ELgp7U4D1QL82G9q9dAdp43k45onPDezGLjHSFGcwCj9, 1000000000000000000)
    let contract = MyToken(#109b05391a240a0d21671720f62fe39138aaca562676053900b348a51e11ba25)
    contract.buy(@1ELgp7U4D1QL82G9q9dAdp43k45onPDezGLjHSFGcwCj9, 1000000000000000000)
  }
}

Here is a brief explanation of this code:

  • approveAlf!(address, amount) authorizes the specified amount of ALPH from the address to be used in the script.
  • The contract is loaded by its id
  • Call MyToken.buy to buy 1000 tokens for 1 ALPH

The next steps are very similar to the previous sections. We will compile, build, sign and submit the script.

Compile a Script

We query the node API to compile the script to binary code. Make sure you append the source code of the MyToken contract after your TxScript code.

curl -X 'POST' \
  'http://127.0.0.1:12973/contracts/compile-script' \
  -H 'Content-Type: application/json' \
  -d '{
  "code": "TxScript Main {\n  pub payable fn main() -> () {\n    approveAlf!(@1ELgp7U4D1QL82G9q9dAdp43k45onPDezGLjHSFGcwCj9, 1000000000000000000)\n    let contract = MyToken(#109b05391a240a0d21671720f62fe39138aaca562676053900b348a51e11ba25)\n    contract.buy(@1ELgp7U4D1QL82G9q9dAdp43k45onPDezGLjHSFGcwCj9, 1000000000000000000)\n  }\n}\nTxContract MyToken(owner: Address, mut remain: U256) {\n  pub payable fn buy(from: Address, alphAmount: U256) -> () {\n    let tokenAmount = alphAmount * 1000\n    assert!(remain >= tokenAmount)\n    let tokenId = selfTokenId!()\n    transferAlf!(from, owner, alphAmount)\n    transferTokenFromSelf!(from, tokenId, tokenAmount)\n    remain = remain - tokenAmount\n  }\n}"
}'

A response similar to the following will be returned:

{
  "code": "010101000100091500c632fb35c006d09c104c9a1075c03adde92636de8a5ea3be934a65cfebb0321813c40de0b6b3a7640000a2144020109b05391a240a0d21671720f62fe39138aaca562676053900b348a51e11ba2517001500c632fb35c006d09c104c9a1075c03adde92636de8a5ea3be934a65cfebb0321813c40de0b6b3a764000016000100"
}

Build an unsigned script transaction

We first obtain the publicKey of the active address:

curl 'http://127.0.0.1:12973/wallets/demo-1/addresses/1ELgp7U4D1QL82G9q9dAdp43k45onPDezGLjHSFGcwCj9'

We get a response similar to:

{
  "address": "1ELgp7U4D1QL82G9q9dAdp43k45onPDezGLjHSFGcwCj9",
  "publicKey": "02d6ab089cd90b9017d209f8acad31b22cd4da2059caf48b0b64a2b6bc9b145be0"
}

Then we build the unsigned transaction:

curl -X 'POST' \
  'http://127.0.0.1:12973/contracts/build-script' \
  -H 'Content-Type: application/json' \
  -d '{
  "fromPublicKey": "02d6ab089cd90b9017d209f8acad31b22cd4da2059caf48b0b64a2b6bc9b145be0",
  "code": "010101000100091500c632fb35c006d09c104c9a1075c03adde92636de8a5ea3be934a65cfebb0321813c40de0b6b3a7640000a2144020109b05391a240a0d21671720f62fe39138aaca562676053900b348a51e11ba2517001500c632fb35c006d09c104c9a1075c03adde92636de8a5ea3be934a65cfebb0321813c40de0b6b3a764000016000100",
  "gas": 80000
}'

We obtain the following response:

{
  "unsignedTx": "0101010101000100091500c632fb35c006d09c104c9a1075c03adde92636de8a5ea3be934a65cfebb0321813c40de0b6b3a7640000a2144020109b05391a240a0d21671720f62fe39138aaca562676053900b348a51e11ba2517001500c632fb35c006d09c104c9a1075c03adde92636de8a5ea3be934a65cfebb0321813c40de0b6b3a76400001600010080013880c1174876e80002d8d380f36d9df13e2b5d0bbb80a186e9a931e3a8044b1392a244274e029553af7c108d760002d6ab089cd90b9017d209f8acad31b22cd4da2059caf48b0b64a2b6bc9b145be0d8d380f3b9df6551974363e5c69ba811c484e94304b0d2c86e61be4850d1872cd4923a140002d6ab089cd90b9017d209f8acad31b22cd4da2059caf48b0b64a2b6bc9b145be000",
  "hash": "3fb33e83cf246fff900c5ff59d2ce3f835816dcf936922471337a7df893325bf",
  "fromGroup": 0,
  "toGroup": 0
}

Sign a script

Next, we sign the transaction hash:

curl -X 'POST' \
  'http://127.0.0.1:12973/wallets/demo-1/sign' \
  -H 'Content-Type: application/json' \
  -d '{
  "data": "3fb33e83cf246fff900c5ff59d2ce3f835816dcf936922471337a7df893325bf"
}'

And we receive the signature:

{
  "signature": "1aa1aa909fca1b24dbf95bc105eceab08992ff88622424d8055531d06ce56d0832d4d468b23c9484bb471c193f912ad96dc3378c670e32527598479de721c750"
}

Submit a script

Finally we can submit the transaction.

curl -X 'POST' \
  'http://127.0.0.1:12973/transactions/submit' \
  -H 'Content-Type: application/json' \
  -d '{
  "unsignedTx": "0101010101000100091500c632fb35c006d09c104c9a1075c03adde92636de8a5ea3be934a65cfebb0321813c40de0b6b3a7640000a2144020109b05391a240a0d21671720f62fe39138aaca562676053900b348a51e11ba2517001500c632fb35c006d09c104c9a1075c03adde92636de8a5ea3be934a65cfebb0321813c40de0b6b3a76400001600010080013880c1174876e80002d8d380f36d9df13e2b5d0bbb80a186e9a931e3a8044b1392a244274e029553af7c108d760002d6ab089cd90b9017d209f8acad31b22cd4da2059caf48b0b64a2b6bc9b145be0d8d380f3b9df6551974363e5c69ba811c484e94304b0d2c86e61be4850d1872cd4923a140002d6ab089cd90b9017d209f8acad31b22cd4da2059caf48b0b64a2b6bc9b145be000",
  "signature": "1aa1aa909fca1b24dbf95bc105eceab08992ff88622424d8055531d06ce56d0832d4d468b23c9484bb471c193f912ad96dc3378c670e32527598479de721c750"
}'

And we receive the txId and groups information:

{
  "txId": "3fb33e83cf246fff900c5ff59d2ce3f835816dcf936922471337a7df893325bf",
  "fromGroup": 0,
  "toGroup": 0
}

Again, we can find the transaction on the mainnet by query the block containing the transaction. We observe the following transaction content:

{
  "id": "3fb33e83cf246fff900c5ff59d2ce3f835816dcf936922471337a7df893325bf",
  "inputs": [
    {
      "type": "asset",
      "outputRef": {
        "hint": -657227533,
        "key": "6d9df13e2b5d0bbb80a186e9a931e3a8044b1392a244274e029553af7c108d76"
      },
      "unlockScript": "0002d6ab089cd90b9017d209f8acad31b22cd4da2059caf48b0b64a2b6bc9b145be0"
    },
    {
      "type": "asset",
      "outputRef": {
        "hint": -657227533,
        "key": "b9df6551974363e5c69ba811c484e94304b0d2c86e61be4850d1872cd4923a14"
      },
      "unlockScript": "0002d6ab089cd90b9017d209f8acad31b22cd4da2059caf48b0b64a2b6bc9b145be0"
    },
    {
      "type": "contract",
      "outputRef": {
        "hint": -527339900,
        "key": "109b05391a240a0d21671720f62fe39138aaca562676053900b348a51e11ba25"
      }
    }
  ],
  "outputs": [
    {
      "type": "asset",
      "amount": "1000000000000000000",
      "address": "1Bw9NuSufuvi1EgWFe9uCQS3xi1gkZ81mtdPRhPbSqw5r",
      "tokens": [],
      "lockTime": 0,
      "additionalData": ""
    },
    {
      "type": "asset",
      "amount": "4992000000000000000",
      "address": "1ELgp7U4D1QL82G9q9dAdp43k45onPDezGLjHSFGcwCj9",
      "tokens": [
        {
          "id": "109b05391a240a0d21671720f62fe39138aaca562676053900b348a51e11ba25",
          "amount": "1000000000000000000000"
        }
      ],
      "lockTime": 0,
      "additionalData": ""
    },
    {
      "type": "contract",
      "amount": "1000000000000",
      "address": "uomjgUz6D4tLejTkQtbNJMY8apAjTm1bgQf7em1wDV7S",
      "tokens": [
        {
          "id": "109b05391a240a0d21671720f62fe39138aaca562676053900b348a51e11ba25",
          "amount": "9999999000000000000000000000"
        }
      ]
    }
  ],
  "gasAmount": 80000,
  "gasPrice": "100000000000"
}

We can see that there is a contract input, with the outputRef.key pointing to the contract we created earlier.

{
  "type": "contract",
  "outputRef": {
    "hint": -527339900,
    "key": "109b05391a240a0d21671720f62fe39138aaca562676053900b348a51e11ba25"
  }
}

This time we have three outputs: two assets and a contract. The first output is the new UTXO for the 1 ALPH payed to the contract owner.

{
  "type": "asset",
  "amount": "1000000000000000000",
  "address": "1Bw9NuSufuvi1EgWFe9uCQS3xi1gkZ81mtdPRhPbSqw5r",
  "tokens": [],
  "lockTime": 0,
  "additionalData": ""
}

The second output is a new UTXO equivalent to the change of the consumed UTXOs for the payment to the contract owner. Additionnally, The first item in the tokens list corresponds to the tokens we just bought ! The id corresponds to the one of our contract.

{
  "type": "asset",
  "amount": "4992000000000000000",
  "address": "1ELgp7U4D1QL82G9q9dAdp43k45onPDezGLjHSFGcwCj9",
  "tokens": [
    {
      "id": "109b05391a240a0d21671720f62fe39138aaca562676053900b348a51e11ba25",
      "amount": "1000000000000000000000"
    }
  ],
  "lockTime": 0,
  "additionalData": ""
}

The third output is the contract after the exchange. We observe that the amount of tokens changed from 10000000000000000000000000000 to 9999999000000000000000000000. The difference is equivalent to the amount of tokens we bought.

{
  "type": "contract",
  "amount": "1000000000000",
  "address": "uomjgUz6D4tLejTkQtbNJMY8apAjTm1bgQf7em1wDV7S",
  "tokens": [
    {
      "id": "109b05391a240a0d21671720f62fe39138aaca562676053900b348a51e11ba25",
      "amount": "9999999000000000000000000000"
    }
  ]
}

Congratulations! You have deployed and used your first smart contract on Alephium ! :rocket:

Contract State

From the previous sections, we can see that:

  • When a contract is created, a contract output will be generated regardless of whether a token is issued or not. If a token is issued, there will be an initial number of tokens in the output tokens list
  • Calling the contract will consume the contract output and generate a new contract output. In the above example, we can see that the contract output generated when the contract is created is consumed, and then a new contract output is generated
  • Calling the contract may also modify the state of the contract. In the above example, it will be modified after calling MyToken.buy.

Let's take a look at what the contract state specifically includes:

final case class ContractState private (
    code: StatefulContract.HalfDecoded,
    initialStateHash: Hash,
    fields: AVector[Val],
    contractOutputRef: ContractOutputRef
)

where the fields are:

  • code Contract code half-decoded. Considering that only part of the code may be involved when calling the contract, so there is no need to completely decode it
  • initialStateHash the hash of the initial contract state
  • fields Vector of state values. AVector(owner, remain) in the MyToken example
  • contractOutputRef Pointer to contract output

The process of calling and changing the state of the contract is roughly as follows:

  • Load the contract state from the WorldState, which is a storage for UTXOs, smart contracts state and code.
  • Load contract output pointed by contractOutputRef according to the contract state (executed when method is payable)
  • When the contract execution involves modifications of the contract state, the contract state in WorldState will be updated
  • If the contract generates a new contract output, the contract state will be updated and the old contract output will be deleted

In addition, we will briefly mention the errors and solutions that may be encountered when creating and calling contracts:

  • NotEnoughBalance: This can only be solved by obtaining mining rewards or transfers by others
  • OutOfGas: The default gas is relatively small, and it is usually not enough when creating and calling contracts, so it is generally necessary to manually specify the gas consumed
  • AmountIsDustOrZero: In order to avoid being attacked, the system will reject outputs with too small amount. If you want to know more, please refer to here

Interested people can try to create various contracts on the mainnet and migrate ETH applications to Alephium.

More on Smart Contracts

We will provide more documents soon. Right now, there are several source codes to learn about our contract language:

  1. simple Uniswap-like contract: https://github.com/alephium/alephium/blob/master/flow/src/test/scala/org/alephium/flow/core/VMSpec.scala#L869-L977
  2. there are more examples in the same file: https://github.com/alephium/alephium/blob/master/flow/src/test/scala/org/alephium/flow/core/VMSpec.scala
  3. an integration test for the Uniswap-like exchange based on Rest API: https://github.com/alephium/alephium/blob/master/app/src/it/scala/org/alephium/app/SmartContractTest.scala
  4. all of the built-ins:
    1. https://github.com/alephium/alephium/blob/master/protocol/src/main/scala/org/alephium/protocol/vm/lang/BuiltIn.scala#L195-L218 and
    2. https://github.com/alephium/alephium/blob/master/protocol/src/main/scala/org/alephium/protocol/vm/lang/BuiltIn.scala#L367-L393

Roadmap

The network is young and growing rapidly. We will update the roadmap to reflect the latest priorities.

Community development

  • Coinmarketcap and Coingecko listings CoinmarketCap listing Done https://coinmarketcap.com/currencies/alephium/ (currently as an untracked project since there is no public market for ALPH) Coingecko application form submitted
  • Exchange listing: this is working in progress with highest priority
  • AMD GPU miner: this is working in progress with high priority
  • Mining pool: initial investigation is done, we might work on it soon
  • Community bounty/grants program: this is started, need more time to polish details

Core development

  • Improve the UX for smart contract development. We will start to build some dApps once the endpoints are more mature
  • Document the core design of Alephium, so that it is easier to onboard new developers
  • Design and implement bridges to other chains. Several friends did some initial investigation
  • Improve the robustness and efficiency of networking modules
  • Improve IO efficiency of full node
  • multi-node clique stabilization

CPU Miner Guide

Note: CPU mining is only for testing purpose. Please visit GPU Miner Guide for mining in production

You must first follow the steps in the Mainnet Guide, in order to download, configure, start your node and use Swagger (or any other openapi clients).

Please note that the default address and port for the REST API is http://127.0.0.1:12973/docs.

Start mining

Please make sure that your local node is fully synced before mining. We will add validation for this in our next major release.

You can start mining on your local node by doing a POST on /miners?action=start-mining.

The server should answer simply with true to confirm that the mining process has now started.

Please note that you will need first to configure your miner's addresses as explain the Create a new miner wallet section of the GPU Miner Guide.

Stop mining

Similarly, you can stop mining on your local node by doing a POST on /miners?action=stop-mining.

CPU Usage

You could tune how much CPU resources for mining by using the following two configs:

akka.actor.mining-dispatcher.fork-join-executor.parallelism-min = 1 // the minimal number of threads for mining
akka.actor.mining-dispatcher.fork-join-executor.parallelism-max = 4 // the maximal number of threads for mining

This article follows a brainstorm between Cheng and Tom

Let’s consider mainly UTXO-based blockchain. The state of such kind of blockchains is mainly about the number of UTXOs. To ease the state explosion issue, we will need a proper design to invalidate UTXOs with small value.

Let’s also assume that all the UTXOs are equally sized. Then the state explosion problem can be reduced to bound the number of UTXOs. As blockchain protocol applies the same validation rules to each UTXO, an upper bound on the number of UTXOs means a lower bound on the number of coins per UTXO (inflation and deflation are not considered as the rates of both are usually not so high ). Therefore, we come to the conclusion that a lower bound is necessary.

However, it’s nearly impossible to find a fixed lower bound for the coin amount per UTXO. For example, for Bitcoin, 1mBTC might be a reasonable lower bound 10 years ago, but it cannot be used anymore nowadays. The second best is to find a dynamic lower bound that depends on the current state of the blockchain. Ideally, the lower bound would make attackers either impossible or cost high to inflate the state. When the market value of the blockchain is low, the cost to attack is low anyway, so the lower bound has to be high to make it impossible to create new utxos insanely. When the market value of the blockchain gets higher, the lower bound can be reduced.

@Tom suggested designing such a lower bound based on averaged tx fee of the latest blocks. However, most of the blockchains would have “gas limit” as Ethereum. When the network is very congested, the tx fee would be equal to the gas limit always. In this case, the lower bound will not be dynamic enough in the long term. However, with a dynamic gas limit like EIP1559, it might perform well.

@Cheng suggested designing the lower bound based on the hash rate of the blockchain. Theoretically, hash rate is a good index for energy consumption and block reward in fiat. In practice, when using a simple and efficient mining algorithm like Sha256, the cost per hash rate is pretty stable and predictable in the long run. The lower bound can be inversely proportional to (hash_rate / coinbase_reward) if follow this design.

As noted by @Tom, there is an edge case that one UTXO might be above the lower bound when it’s created, but later might be invalid with a new lower bound. There are two ways to deal with it: 1) actively removing those invalid UTXOs, but this will probably make users angry and also make the system complicated; 2) just leave it there and users will still be able to use it as each tx can include multiple inputs. Note that this is not possible for Ethreum's native token, as there can be only one caller for each Eth tx.