Qwai Get the wallet

Overview

Qwai is a proof-of-stake layer 1 blockchain built without elliptic-curve cryptography. Every account and validator signs with post-quantum keys, blocks are final as soon as they are made, and smart contracts are Rust programs run on a RISC-V virtual machine.

CoinQWAI. Six decimals: 1 QWAI is 1,000,000 smallest units.
Supply1,000,000,000 QWAI, fixed. No inflation.
ConsensusProof of stake, Byzantine fault tolerant. One block to finality.
SignaturesML-DSA-65 (FIPS 204). Optional second key: SLH-DSA (FIPS 205).
HashingBLAKE3.
ContractsRust, compiled to RISC-V (RV32IM), gas metered.
Addresses32 bytes, written as 64 hex characters, no prefix.
Public nodehttps://rpc.qwai.io/api
Explorerexplorer.qwai.io

Get started

  1. Install Qwail. Download the wallet from qwail.org and add it to Chrome.
  2. Create a wallet. Qwail shows 24 recovery words. Write them down and keep them offline. Anyone with the words controls the wallet, and nobody can restore it without them.
  3. Receive QWAI. Press Receive and share your address.
  4. Use apps. On Qwaix or any Qwai app, press Connect. Every request from an app is shown to you in Qwail before anything is signed.

Accounts and keys

An account is not one key. It is a rule: "any k of these n keys must sign". The address is a hash of the first rule the account was made with, and it never changes afterwards.

Two-key protection

An account can hold a lattice key (ML-DSA) and a hash-based key (SLH-DSA) and require both on every transaction. The two rest on unrelated mathematics, so an attacker has to break both. Turn it on in Qwail under Settings. Two-key transactions are about three times larger and take about a second to sign.

Changing keys

Keys can be replaced or added at any time with one transaction. The address, balance, tokens and NFTs stay exactly where they are. If a signature scheme is ever weakened, accounts move to new keys without moving funds.

Fees

A transaction pays one smallest unit of QWAI per byte, plus gas if it runs a contract (1,000 gas per smallest unit). A plain transfer costs about 0.0034 QWAI; a token trade about 0.004. Fees go to the validator that proposes the block.

Validators and staking

Validators take turns proposing blocks in proportion to their stake, and a block is final once validators holding more than two thirds of all stake have signed it.

Run a validator

  1. Download the Qwai Node app from Run a node and open it. It joins the network and verifies the whole chain.
  2. In the app, create or import a wallet holding at least 10,000 QWAI.
  3. Press Stake. Your computer starts signing blocks in the next block.
Smallest stake10,000 QWAI
RewardsThe fees of every block you propose.
WithdrawingStake unlocks 14 days after you stop validating.
Going offlineAfter 200 blocks in a row without your signature, your validator is set aside. Your stake is untouched; press Rejoin in the app.
Signing twiceSigning two different blocks at one height burns 5% of the stake and removes the validator for good. Never run the same validator on two computers.

Stake behind a validator

You do not need to run a computer to stake. Any amount from 1 QWAI can be staked behind a validator that takes delegations. Your coins add to its voting power and earn a share of the fees from every block it proposes. Rewards are added to your stake automatically.

  1. Open the Validators page and connect Qwail.
  2. Press Stake next to a validator, enter an amount and approve it in the wallet.
  3. To leave, press Unstake. The coins can be collected 14 days later.
Smallest amount1 QWAI
RewardsYour share of the validator's block fees, less its commission. Qwai creates no new coins, so rewards come only from fees.
CommissionSet by the validator. It can be lowered at any time and raised by at most 1% a day.
Unstaking14 days. Coins that are unstaking earn nothing.
RiskIf the validator signs two different blocks at one height, 5% of everything staked behind it is burned, yours included. Coins still unstaking at that moment are included.
If the validator leavesYour coins stop earning and are free when the validator's own are. Press Unstake to take them back.

Validator owners open their validator to delegators, and set the commission, in the Qwai Node app.

Burn address

000000000000000000000000000000000000000000000000000000000000dead

No key can ever exist for this address. Coins, tokens and NFTs sent to it are gone for good. Coins held there are counted as burned and taken off the supply shown by the chain.

Live validators and their shares are on the Validators page.

Tokens and Qwaix

Every token launched on Qwaix runs the same program: a fixed supply of 1,000,000,000 with six decimals, no owner, and no way to make more.

Launching

A launch puts the whole supply into a pool against QWAI. The creator holds nothing they did not buy. The price follows a curve; when the pool has raised its target, it opens as an ordinary pool, its starting liquidity is locked for good, and anyone can add liquidity.

Tax

A creator can set a tax on every buy and sell, fixed forever at launch, split three ways:

To holdersPaid in QWAI to everyone holding the token, in proportion to what they hold. Holders press Claim.
To the creatorPaid in QWAI on every trade.
Buy-back and burnSpent buying the token from its own pool and destroying it.

The three together can be at most 25%. Sending a token from one wallet to another is never taxed.

Fees

While a pool is launching, 1% of each trade is a fee. After it opens, 0.3% of each trade stays in the pool for liquidity providers.

NFTs

An NFT collection is one contract running the standard NFT program. Each item has a number, one owner, and a description stored on the chain: a name, a picture, text and traits. Wallets and explorers recognise every collection automatically because they all run the same program.

A collection is either closed (only its creator mints) or open (anyone mints by paying a price that goes to the creator), with an optional limit on how many items can ever exist. A collection can ask for a creator's share of every sale, up to 10%. See Standards for the calls.

Marketplace

NFTs are created, bought and sold on Qwaix. Selling never moves your item out of your wallet: you let the market move it, set a price, and keep holding it until someone pays. Offers hold the bidder's coins in the market contract until they are accepted or taken back. Each sale pays the creator's share and a 1% market fee; the seller receives the rest.

A listing ends for good if the item moves or you take the market's permission away. It does not come back if the item returns or permission is given again.

Traits

Give each item a traits object in its description, such as {"Background": "Night", "Level": 3}. Marketplaces build their filters and rarity ranks from it.

Smart contracts

A contract is an ordinary Rust program (#![no_std], #![no_main]) compiled for RISC-V. It is not Solidity and there is no EVM. The developer kit has the toolkit, a starter contract and the real token, NFT and exchange contracts.

Build

curl https://sh.rustup.rs -sSf | sh     # install Rust once
./build.sh                              # in the kit folder; output: build/<name>.elf

A complete contract

#![no_std]
#![no_main]
use contract_sdk as chain;

const TOTAL: [u8; 32] = [1; 32];

fn main() {
    let mut input = [0u8; 64];
    let len = chain::input(&mut input);
    if chain::is_deploy() { return; }            // one-time setup goes here
    match input[..len].split_first() {
        // 01 + amount: add to the total
        Some((1, args)) if args.len() == 8 => {
            let mut now = [0u8; 8];
            chain::storage_get(&TOTAL, &mut now);
            let add = u64::from_le_bytes(args.try_into().unwrap());
            let Some(next) = u64::from_le_bytes(now).checked_add(add) else { chain::revert(b"too large") };
            chain::storage_set(&TOTAL, &next.to_le_bytes());
            chain::emit(&chain::caller());
        }
        // 02: read the total
        Some((2, [])) => { let mut now = [0u8; 8]; chain::storage_get(&TOTAL, &mut now); chain::ret(&now) }
        _ => chain::revert(b"unknown call"),
    }
}

chain::entry!(main);

How a contract runs

  • It runs from the top each time it is called. The first input byte chooses the call; the rest are its arguments, fixed-width and little-endian.
  • is_deploy() is true for the one run when the contract is first put on the chain.
  • Memory is one megabyte, zeroed at the start of every call. There is no heap; use fixed-size arrays.
  • revert (or a panic) fails the call and undoes everything it did, including coins it was sent.
  • Release builds do not check arithmetic for overflow. Use checked_add and friends.
  • A contract cannot be changed after it is deployed.

What a contract can ask of the chain

input(&mut buf)Copies the call's input; returns its full length.
ret(&bytes)Ends the call successfully with an output.
revert(message)Ends the call and undoes everything it did.
caller()Who called: a wallet or another contract.
this()This contract's own address.
value()Coins sent with this call.
storage_get(&key, &mut out)Reads a stored value. Keys are 32 bytes.
storage_set(&key, &value)Stores up to 4,096 bytes. An empty value deletes.
transfer(&to, amount)Sends coins the contract holds. Runs no code at the receiver.
balance(&address)Coin balance of any address.
emit(&bytes)Records an event, up to 1,024 bytes, 64 per transaction.
hash(&bytes)BLAKE3. Use it to make storage keys.
block()Height and time (milliseconds) of the current block.
call(&contract, value, &input, &mut out)Calls another contract, up to 8 deep. If it fails, its effects are undone.
code_id(&address)Identity of the program at an address, so a contract can accept only known programs.
is_deploy()True during the deploying run.

Gas and limits

Per instruction1 gas
Storage write20,000 gas + 50 per byte
Storage read2,000 gas
Calling another contract30,000 gas
Sending coins10,000 gas
One transactionup to 5,000,000 gas
One blockup to 50,000,000 gas
Price1,000 gas per smallest unit of QWAI

Habits that keep contracts safe

  • Check every input length exactly and revert on anything unexpected.
  • Refuse coins on calls that do not use them, or they are stuck in the contract.
  • Keep your own count of what the contract holds. Never decide anything from the contract's balance, which anyone can change by sending coins.
  • Update your stored state before calling another contract, and guard against being called back mid-call.
  • Round in the contract's favour.

Standards

Calls are a selector byte followed by fixed-width arguments. Amounts and item numbers are 8 bytes, little-endian. Addresses are 32 bytes.

Token

01transfer: to, amount
02balance: of → amount
03supply → amount
04 / 05 / 06name, symbol, decimals
07info → settings, totals, name, symbol
08details → what the creator stored (picture, links)
09owed: of → QWAI waiting for a holder
0aclaim: of. Pays a holder what they are owed
0bburn: amount

NFT collection

Program id 6aa367b3c8e72a23f5c006c501ea1ab403753325e34106530150c59edb7c5201

01transfer: to, item
02owner: item → address
03supply → made, existing, most
04 / 05name, symbol
08details → the collection's own description
09item: item → owner, then its description (JSON)
0amint: to, description → item number
0bburn: item
0cbalance: owner → count
0downed: owner, first, count (1 byte) → item numbers
0eapprove: item, account. Lets that account move it once
0fapproved: item → address
10take: item, to. By an account allowed to move it
11allow: account, yes (1 byte). Lets that account move any of your items until undone
12allowed: owner, account → 1 byte
13holder: item → owner, times moved
14standing: item, account → owner, times moved, two permission counts, allowed, approved

Deploy input: most items (8, 0 for no limit), mint price (8), open to all (1), creator's share in hundredths of a percent (2, at most 1000), then name, symbol and details, each after its length.

An item's description is JSON: {"name", "image", "about", "traits"}. The image is a data:image/...;base64, picture or an https:// address. Descriptions are at most 3,800 bytes.

NFT market

01list: collection, item, price, expiry
02unlist: collection, item
03buy: collection, item, price. Send the price
04offer: collection, item (0 for any), price each, how many, expiry. Send the total
05withdraw: collection, item, bidder
06accept: collection, item, bidder, the offer's item (or 0), least price
07 / 08 / 09listing, listings (a page), offers (a page)
0a / 0btotals for a collection, settings

Times are milliseconds; an expiry of 0 means never. The market only trades collections running the standard NFT program.

Token lock

Program id 687e0605495ba0a96d24db29e62ee9ebec50cea430520a1b9cb34f5ed9e6747f

One lock is one contract. Whatever is sent to it, of one token and of QWAI, is released to one receiver on one schedule. Nobody, its creator included, can take anything back or change the schedule.

Deploy inputtoken (32, zeros for QWAI only), receiver (32), start, first release, end (8 each, milliseconds)
01release. Anyone may call; pays the receiver what has unlocked
02info → settings, paid, held, releasable now
03pass on: new receiver. By the receiver
04add coins

Create and release locks on Qwaix.

API

Every node answers over HTTPS with JSON. No key and no sign-up; any website may call it. Amounts are text in whole coins. Contract input and output are hex. There are no push updates: ask /status every couple of seconds and fetch what is new. The explorer has a live reference where every call can be tried.

GET /statusHeight, supply, staking totals.
GET /account/ADDRESSBalance and next transaction number.
GET /blocks, /block/HEIGHTBlocks and their transactions.
GET /tx/ID, /receipt/IDA transaction and what it did.
GET /address/ADDRESS/transactionsHistory of an address.
GET /validatorsValidators, stake, shares, commission and delegated coins.
GET /delegations/ADDRESSWhat an account has staked behind validators.
GET /nft/collections, /nft/collection?id=NFT collections with floor, volume and traits.
GET /nft/items?collection=&traits=Items, filtered by trait, price, status or owner, and sorted.
GET /nft/item, /nft/offers, /nft/activity, /nft/accountOne item, a collection's offers, recent activity, one account's NFTs.
GET /contract/ADDRESSWhether a contract is there, and its program id.
GET /contracts?program=IDEvery contract running one program.
GET /events?contract=&about=A contract's events, newest first.
POST /queryRuns a contract read-only. Body: {"contract", "input"}.
POST /queriesUp to 25 of those at once.
POST /txSubmits a signed transaction.
GET /dex/tokens, /dex/token, /dex/holdersQwaix tokens, one token, its holders.
GET /proof/ADDRESSA balance with a proof against the signed state.

Connecting the wallet

Qwail gives every page window.qwail. Your site asks, the user approves in the wallet, and the wallet signs and sends. Your code never touches a key.

if (!window.qwail) { /* send the user to https://qwail.org */ }

const { address } = await window.qwail.connect();

// Call a contract. input is hex: selector byte, then arguments.
// value is QWAI to send along, as text in whole coins.
const result = await window.qwail.signAndSend({
  kind: 'call', contract: '<address>', input: '01' + to + u64(amount), value: '0',
});
// Resolves once it is in a block: { id, ok, output, created }
// Rejects if the user declines or the contract reverts.

// Put a contract on the chain. code is the .elf file as hex.
const made = await window.qwail.signAndSend({ kind: 'deploy', code, input });

// A plain payment.
await window.qwail.signAndSend({ kind: 'transfer', to: '<address>', amount: '2.5' });

Encoding helpers

// A number as 8 little-endian bytes of hex.
const u64 = (n) => { let v = BigInt(n), s = '';
  for (let i = 0; i < 8; i++) { s += (v & 255n).toString(16).padStart(2, '0'); v >>= 8n; }
  return s; };
// The reverse, from a hex reply.
const readU64 = (hex) => (hex.match(/../g) || [])
  .reduceRight((n, h) => (n << 8n) | BigInt(parseInt(h, 16)), 0n);

Building with AI

Qwai is new, so an AI assistant will not know it by default. Give it the guide and the kit and it has everything it needs.

  1. Download the developer kit and open the folder in your AI coding tool.
  2. Point it at QWAI-DEV-GUIDE.md, sdk/ and examples/. The guide is also available as plain text.
  3. Describe what you want.
Using QWAI-DEV-GUIDE.md, the SDK in sdk/ and the contracts in examples/
as the reference for style and safety, write a Qwai contract that
[what it should do]. Use the first input byte as the call selector,
fixed-width little-endian arguments, checked arithmetic, and revert on
any unexpected input. Then write a single-page site that reads it
through POST /api/queries and sends transactions through window.qwail.