This page takes a token from nothing to a live, transferable asset on Rand. RPL is Rand’s token standard, new on chain 14. If you have deployed an ERC-20 or created an SPL mint, the first section is the part to read slowly, because an RPL token is neither. RPL tokens is the concept page behind this one.
1. What an RPL token is
An RPL token is an entry in the ledger’s token registry, not a smart contract. There is no code to write, compile or audit. Creating a token is one transaction that adds a row to the registry, and from then on the token’s units are ordinary shielded notes, held in the same pool as RAND and spent the same way.
| field | rule |
|---|---|
| asset id | 32 bytes, the token’s identity. Written rpl1…: bech32m, 62 characters, checksummed |
| index | a small number the chain assigns at registration (1, 2, …; 0 is RAND). A shorthand for the id on this chain |
| name | 1 to 32 bytes of UTF-8, fixed at creation |
| symbol | 1 to 12 printable ASCII characters, fixed at creation |
| decimals | 0 to 9, fixed at creation |
| mint authority | who may issue more units: nobody (None), a key (Key), or the bridge (Bridge) |
| total supply | public, and moved only by a mint or a burn |
Symbols are not unique. Anyone can create a token called USDC, exactly as on Ethereum and Solana. A token’s identity is its asset id, so check the rpl1… id before you accept or list a token, never the symbol. The text form is checksummed: a mistyped id is refused, not sent somewhere else.
How it differs from ERC-20 and SPL
| ERC-20 / SPL | RPL | |
|---|---|---|
| what a token is | a contract, or an account owned by a program | a registry row; no per-token code, so no transfer tax, rebase or hook |
| balances | public, per address | shielded notes. Nobody can read a holder’s balance or list a token’s holders |
| a transfer | public sender, recipient and amount | proved by the sender, fully shielded. An observer cannot tell it from a RAND payment |
| fee | the chain’s native coin | RAND. A holder needs a little RAND to move a token, as an ERC-20 holder needs ETH |
| receiving | SPL needs a token account first | nothing to set up. Any rand1… address can receive any token |
| amounts | uint256 / u64 | u64, and at most 9 decimals |
| approve / transferFrom | yes | not in v1 |
| freeze, blacklist, pause | often | none |
| metadata | off chain or mutable | on chain and immutable |
| held by a program | yes | not in v1 |
What stays public is the boundary: a token’s registration, every mint and every burn, each with its amount. That is what makes the total supply auditable by anyone. Transfers between holders reveal nothing, including which token moved.
2. Before you start
| you need | how |
|---|---|
the rand command line | Running a node builds it; Command line lists every command |
| a wallet | rand keygen writes a spend-key file and rand address prints its rand1… address (Accounts and addresses) |
| a node to talk to | the wallet speaks JSON-RPC to a node, by default your own at http://127.0.0.1:8545. A node binds its RPC to localhost, so the usual setup is a node of your own following chain 21. Without one, pass --rpc https://rpc.randprotocol.org: the public endpoint carries everything on this page except rand faucet, which it refuses (JSON-RPC, the public endpoint) |
| RAND for fees | a few RAND in the wallet. One faucet call’s 100 RAND covers everything on this page, but on chain 21, as on chain 20, rand faucet pays only the 16 wallets the genesis allowlists (staking.faucet_recipients) and refuses any other address with the faucet may not mint to this recipient; any other wallet needs RAND sent to it by someone who holds some (Mint and burn) |
| the registration fee | a genesis parameter of the chain, between 1 and 10,000 RAND. On chain 21 it is 1 RAND, as on chain 20, so rand token create costs 1.001 RAND: the 0.001 RAND base every transaction pays, plus the registration fee, which every chain since 15 burns rather than paying it to the block’s proposer; on chain 21 the base is burned too (genesis fees.burn_floor). Every other command here costs the 0.001 RAND base |
| about 6 GB of free memory | every transaction is proved on your own machine. Since chain 17 that is two proofs, and the wallet prints a line for each: proving the spend authorisation on this machine (tier 10; seconds)…, then proving the bundle (tier 14; about a minute and a half on a laptop)…. The bundle proof takes about 100 seconds and about 5.7 GB of memory while it runs; the two are about 1.37 MB and about 1.5 MB on the wire, about 2.85 MB together. That holds for every step below that changes the chain, the create, each mint, each send and each burn |
rand --key wallet.key.json keygen # writes the spend key; refuses to overwrite
rand --key wallet.key.json address # your rand1… address
rand --key wallet.key.json faucet # testnet only, allowlisted wallets only: asks a validator node to mint 100 RAND to this wallet
rand --key wallet.key.json balance # scans the tree, prints the spendable value and the unspent note count
--key defaults to wallet.key.json and can be set once as RAND_KEY; --rpc (or RAND_RPC) names the node to talk to and defaults to http://127.0.0.1:8545. The commands below spell --key out. Before you spend a proof, check that the node is on chain 21: the JSON-RPC method rand_chainId returns 21 there. The wallet must be a chain-21 build, v0.8.0: chain 21 runs constraint set 9, so an older wallet’s proofs do not decode there, and like chain 20 it binds every proof and every signed message, a token mint’s included, to its genesis hash, so a v0.6.7 rand makes proofs and signatures chains 20 and 21 refuse.
3. Create the token
token create takes its amounts in the token’s smallest unit. --fixed-supply and --initial are whole numbers of base units: with --decimals 2, an amount of 1000000 is 10,000.00 tokens. Every later command (rand send --asset, rand token mint, rand token burn) has taken the token’s display units since v0.5.10, as RAND amounts are in RAND (1.5): the wallet reads decimals from the token’s registry row, and more fractional digits than the token has is refused. On chain an amount is always the base-unit integer.
Decide one thing first: can more units ever be issued? The answer is fixed into the token at creation and cannot be widened later.
| fixed supply | mintable | |
|---|---|---|
| mint authority | None | Key: a Dilithium2 key you hold |
| supply | the whole supply is minted once, in the creating transaction | starts at zero, or at --initial; the key holder mints over time |
| flags | --fixed-supply <N> --to <rand1…> | --authority-key-out <FILE>, and optionally --initial <N> --to <rand1…> |
| fits | a capped token, a membership or voting unit | an issuer that grows supply, such as a points or reward token |
A fixed-supply token
rand --key wallet.key.json token create \
--name "Example Credits" \
--symbol EXC \
--decimals 2 \
--fixed-supply 1000000 \
--to rand1…
--to is the full rand1… address that receives the supply, for example the output of rand address. --fixed-supply is in base units: 1000000 at 2 decimals is 10,000.00 EXC.
The command reads the registry’s next index and the registration fee from the node, builds the initial mint’s note, proves one fee bundle, submits the registration and waits for the block. On success it reports the submission, then prints a line of the form index <N>, id <64 hex> (<rpl1…>), then committed at height <H>. Write the rpl1… id down: it is the token’s identity, and every later command takes it.
The default fee is the base plus the chain’s registration fee, 1.001 RAND on chain 21 as on chain 20, so there is nothing to work out; --fee (in RAND) raises it. --no-wait returns as soon as the transaction is submitted and skips the committed line. --cuda proves on an attached NVIDIA GPU, needs a build with --features cuda, and does not fall back to the CPU.
If another token is registered while yours is proving, it takes the index yours was sealed for. The node refuses the registration and the command prints another token took index N first — re-run to register at N+1, with the real numbers. A refused registration never reaches a block; what is lost is the proving, about 100 seconds of work. Run the command again and it proves against the next index.
The entire supply now sits in one shielded note owned by the --to address. The registration and its amount are public; what the recipient does with the note afterwards is not.
A mintable token
rand --key wallet.key.json token create \
--name "Example Points" \
--symbol EXP \
--decimals 0 \
--authority-key-out exp-authority.key.json
This writes a new Dilithium2 key to exp-authority.key.json (mode 0600, refusing to overwrite an existing file) and registers the token with that key as its mint authority and a supply of zero. The output is the same two lines as for a fixed-supply token. Exactly one of --fixed-supply and --authority-key-out must be given.
To mint a first amount in the same transaction, add --initial and --to, which must be given together:
rand --key wallet.key.json token create \
--name "Example Points" \
--symbol EXP \
--decimals 0 \
--authority-key-out exp-authority.key.json \
--initial 5000 \
--to rand1…
While the registration is in flight the key sits at exp-authority.key.json.pending. It is promoted to exp-authority.key.json once the chain accepts the registration and discarded if the chain refuses it. If the connection fails with the outcome unknown, the file stays at .pending: check rand token list before you delete it.
The key file is the power to print the token. Keep it offline and backed up; it is separate from your wallet’s spend key and is never needed to send or receive.
Why the first mint’s recipient is part of the id
A registration is not signed by its creator. Anyone who sees yours waiting in the mempool can submit a copy of it, paid for with their own fee bundle. If the asset id covered only the name, symbol and amount, that copy could name the copier’s address as the recipient and reach a block first: they would own the id and, for a fixed-supply token, the whole supply for ever.
So the id is a hash over everything in the registration, the initial mint included: its amount, its recipient, its randomness, its time and its encrypted envelope. A copy with a different recipient hashes to a different asset id. It is a different token that happens to share your symbol, which is all a non-unique symbol ever promised. Your id can only ever be registered with your recipient.
--salt <hex32> sets the 32-byte salt that also feeds the id. Leave it out and the wallet picks one at random. Set it when you need the id to be reproducible, for example to publish the id before the token exists.
Confirm it
rand token info rpl1… # by id: rpl1… or 64 hex characters
rand token info 2 # or by the index `token create` printed
rand token list --limit 20 # one page of the registry; --from <index> pages on
token info prints the token’s row as JSON: index, id, id_text (the rpl1… form), name, symbol, decimals, authority, mint_nonce, total_supply and registered_at. authority is {"kind":"none"} for a fixed-supply token and {"kind":"key","key":…,"address":…} for a mintable one. total_supply is a decimal string in base units. token list prints enabled, registration_fee, next_index and the tokens rows. Both read the node’s whole listing, so the node does not learn which token you were interested in.
Or over JSON-RPC, where the parameter may be the index, the 64-character hex id or the rpl1… form:
curl -s http://127.0.0.1:8545 -H 'content-type: application/json' -d '{
"jsonrpc": "2.0", "id": 1,
"method": "rand_getToken",
"params": ["rpl1…"]
}'
The result is the same row token info prints, or null if no such token exists; a malformed id (a bad checksum, mixed case, a wrong length) is error -32602. rand_getTokens takes [from_index, limit] and pages through every registered token, and rand_getTokenSupply returns { total_supply, backings } alone. None of them needs a key. See JSON-RPC.
A row that exists on chain 21 today is zUSD, the bridged token at index 1 — the only token in chain 21’s genesis; the eleven tokens registered on chain 20 (durian.market’s DUR and its pool shares, the example tokens) were not carried over the cut, as no registered token ever is. The explorer serves the node’s registry without a node of your own:
curl -s https://randscan.org/api/v1/tokens
On chain 14, on 2026-09-20, that returned "registration_fee": 1000000000, "next_index": 2 and one token, shortened here to its first backing:
{
"index": 1,
"id": "32e5ab28c782c663e14da2650a3feb12f16a12db85599f4f62dc169d26f37b1f",
"id_text": "rpl1xtj6k2x8strx8c2d5fjs50ltztck5ykms4ve7nmzmstf6fhn0v0spelqtx",
"name": "Shielded USD",
"symbol": "zUSD",
"decimals": 8,
"authority": {
"kind": "bridge",
"backings": [
{ "chain": 2, "token": "000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7",
"decimals": 6, "locked": "0", "minted_today": "0", "mint_day": 20716,
"mint_cap_per_day": "10000000000000" }
]
},
"mint_nonce": 0,
"total_supply": "0",
"registered_at": 256
}
On chain 21 the row has the same id, id_text, name, symbol and backings, but zUSD is listed in the genesis rather than registered at height 256 (registered_at 0), and its supply started at 100.001999 zUSD: 22 locked on Ethereum USDT, 23.001999 on BSC USDT, 30 on Tron USDT and 25 on Solana USDT, carried from chain 20. (Chain 20’s started at 99 zUSD, carried from chain 19; chain 19’s at 10 zUSD, all on Solana; chain 18’s as 9 on Tron and 1 on Solana.) Each backing row also carries the rolling-window fields minted_in_window, mint_window_secs and mint_headroom.
The explorer’s copy of registration_fee is a JSON number; the node’s own RPC returns it, like every amount, as a decimal string. On chain 21, as on every chain since 15, the node’s rand_getTokens also carries max_tokens (4096), burn_registration_fee and bound_note_value (both true) (RPL tokens).
4. Mint, and hand over the authority
Only a token with a Key authority can be minted, and only by that key.
rand --key wallet.key.json token mint \
--asset rpl1… \
--to rand1… \
--amount 5000 \
--authority-key exp-authority.key.json
--amount is in the token’s display units: 5000 of this 0-decimal token is 5,000 base units, and 1.5 of a 6-decimal token would be 1,500,000. Before it builds a note or a proof, the wallet reads the token’s row and refuses if the token is not Key-authorised or if --authority-key is not the key the registry names. On success it reports the submission and prints minted <amount> (<base units> units) of asset <index> to <address>. --fee (default 0.001 RAND), --no-wait and --cuda work as they do for token create.
A mint appends one note for the recipient and raises the total supply. The amount and the recipient’s public key are on the wire: a mint is a public event by design, the same as a deposit through the bridge. Once the recipient moves the note, it is shielded like everything else. The wallet that pays the fee (--key) and the key that authorises the mint (--authority-key) are different keys and can be held by different people.
Each mint is signed over the chain id, the asset id and a counter the registry keeps, and since chain 20 over the genesis hash too (rand-rpl-mint-2), so a mint cannot be replayed on this chain or copied to another, even one that shares the chain id. set-authority is bound the same way.
To move the authority to a new key, or to give it up for good:
rand --key wallet.key.json token set-authority \
--asset rpl1… \
--authority-key exp-authority.key.json \
--new-key next-authority.key.json
rand --key wallet.key.json token set-authority \
--asset rpl1… \
--authority-key exp-authority.key.json \
--renounce
--new-key takes a key file and hands the token to that file’s public key; exactly one of --new-key and --renounce must be given. The command prints asset <index>'s mint authority is now <public key hex>, or asset <index>'s mint authority is renounced for good. Each costs the 0.001 RAND base and one round of proving. --renounce turns the token into a fixed-supply token at its current supply. It is one-way: an authority can pass from key to key or from a key to nobody, and nothing else. A token created with a fixed supply can never become mintable.
5. Use it
Send
rand --key wallet.key.json send rand1… 250 --asset rpl1…
This sends 250 of the token in display units: 250 EXP at 0 decimals, or 250.00 EXC (25,000 base units) at 2. The recipient and the amount are positional, as they are for RAND. Before anything proves, the wallet prints one line, to fingerprint XXXX-XXXX-XXXX-XXXX · <amount> <symbol> (<base units> units), and asks send? [y/N]. Check the fingerprint against the recipient’s and the base units against what you meant. --yes skips the question, and a script must pass it: without it, a stdin that is not a terminal is refused. --asset takes the index, the rpl1… id or the 64-character hex id, and leaving it out sends RAND. --memo <text> seals up to 510 bytes of text to the recipient inside the payment’s envelope, which chain 18 is the first chain to allow; nothing on chain shows that a memo is there. --fee is always in RAND and defaults to 0.001; --no-wait and --cuda apply. An id is resolved to its index from the node’s whole registry listing, and a numeric index never reaches the node at all, so the node is not told which token is about to move. This is the same rand send that moves RAND, and the same proving. The transaction publishes four nullifiers, four commitments, a RAND fee, a time, four encrypted envelopes, the bundle proof and, since chain 17, an authorisation commitment and its proof, whichever asset moved. An observer cannot tell a token transfer from a RAND payment, cannot tell which token it was, and sees no sender, recipient or amount.
The fee is paid in RAND out of the same bundle, so the sending wallet must hold some RAND. The wallet checks that before it starts proving.
Check a balance
rand --key wallet.key.json asset-balance 2 # one token, by index
rand --key wallet.key.json asset-balance # a row for every token this wallet holds
The argument is a registry index. For one token the wallet prints asset <index> balance: <N> units, in base units. rand notes lists every note the wallet has opened with its asset index. The wallet finds its notes by scanning the chain with its viewing key. Nothing on the chain holds a balance for it to look up.
Burn
rand --key wallet.key.json token burn rpl1… 100
The asset and the amount are positional: the asset as an index, rpl1… or hex, the amount in the token’s display units, as send --asset reads it. --fee defaults to 0.001 RAND; --no-wait and --cuda apply.
A burn destroys units and lowers the total supply. Like a mint, it is a public boundary: the amount and the token are visible, the holder is not. A bridged token such as zUSD is not burned this way: token burn refuses it before proving, and it leaves through rand bridge-burn, which releases the coin behind it.
6. Verify it on the explorer
randscan.org shows the public side of a token to everyone: its registration, its mints and burns, and its total supply. Every token is listed at randscan.org/tokens, and one token’s page is /tokens/<rpl1…>, /tokens/<64 hex> or /tokens/<index>; randscan.org/tokens/1 is zUSD. The same data is at GET /api/v1/tokens and GET /api/v1/tokens/:id, the second with the token’s deploy transaction and its supply history of registration, mints and burns (Explorer API).
A transfer’s public page shows no asset, no amount and no parties, because the chain does not have them. To prove a payment to someone else, such as an auditor, a counterparty or an exchange, give them a key:
| key | what its holder sees |
|---|---|
| a transaction key | one output of one transaction opened: the token, the amount and the parties |
| a viewing key | every note the wallet has sent or received, with the same detail, and whether each is spent |
rand --key wallet.key.json tx-key <TX_HASH> # the keys for one transaction
rand --key wallet.key.json viewing-key # the wallet's viewing key, 64 hex characters
tx-key prints one row per output of that transaction this wallet sent, received or kept as change: the output (bundle:0 to bundle:3, the bundle’s four slots), the role, the amount (in RAND, or N (asset I) for a token) and the per-transaction key that output was sealed under. Hand over the key of the sent row. It works for any past transaction, because the wallet recovers the key from the chain.
Paste a transaction key into the transaction’s page on randscan to open that output; the explorer resolves the asset index against the registry and shows the token by name, with its decimals applied. A viewing key goes into randscan.org/viewing. The explorer holds no key and decrypts nothing on its server. Over JSON-RPC the same disclosure is rand_checkTransaction(hash, key). Neither key can spend anything. A viewing key cannot be taken back once shared, so prefer a transaction key when one payment is all that needs proving.
7. Bridged tokens
zUSD (“Shielded USD”) is an RPL token too, with a Bridge authority: its units are minted only when guardians attest a deposit of USDT or USDC on Ethereum, Binance Smart Chain, Tron or Solana, and its supply always equals what is locked there. It was registered on chain 14 by transaction at height 256, at index 1, with seven backings, and the genesis of every chain since, chain 21 included, lists it again under the same id, rpl1xtj6k2x8strx8c2d5fjs50ltztck5ykms4ve7nmzmstf6fhn0v0spelqtx. Chain 15 carried over the 10 zUSD still outstanding when chain 14 stopped, chains 16 to 19 started with the same 10 zUSD, chain 20 started with the 99 zUSD outstanding on chain 19, and chain 21 with the 100.001999 zUSD outstanding on chain 20. Since chain 20 the bridge takes 10 bps of each deposit and each redemption, in zUSD, to a treasury note (Bridge). zUSD is a basket: its backings are pooled at face value, so a holder bears the risk of every listed coin, not only of the one they deposited (RPL tokens). Its supply moves with every deposit and redemption; rand token info 1 or the explorer’s /tokens/1 gives the current figure.
Its life on chain 14, an earlier testnet (checked on the explorer 2026-09-20). This table is chain 14 history: chain 14 is retired, so these transactions are not on the chain-20 explorer:
| what | transaction (chain 14) | height |
|---|---|---|
the registration (register_bridged_token) | 7fa28fe6277a82401dcc440ff32da13c5e5763f79826fb17cc508f4c15abdd13 | 256 |
a bridge mint (bridge_attest) | f2f24d75bc0e7d84dff1f4e4518a9d4bef115392492a39befca1528a2404851d | 2,572 |
| a shielded zUSD transfer | e6ddaca3c852e6c861524a55d00a0fbdcaff6867544b9f19b9bf60ce263c32ed | 3,259 |
a bridge burn (bridge_burn) | f9f365ca170a5fb51ff9381d7b5cc39538c1a621e5f6e8c1bd74d8751c7bbc49 | 3,481 |
The transfer’s page showed what any token transfer shows: four nullifiers, four commitments and a fee, and nothing that says zUSD moved.
To show what a key discloses, the fullnode’s v0.5 release notes published two for that chain-14 transfer, from a demo wallet made for the purpose that holds nothing else:
| key | value | what it opens |
|---|---|---|
| the transfer’s transaction key | 7f68f57c4f33a3f0fe419f4d1a5df2875461339dbc454af8ced50cbd7f40e4ef | output bundle:0 of that one transaction: amount 100000000 of asset 1, which is 1 zUSD |
| the demo wallet’s viewing key | a93c673ffeff7204447161347828d8e549b7070460813ae1b7e664694e1dcd5d | every note that wallet has sent or received |
On chain 14 the transaction key opened that output on the transfer’s page, and over JSON-RPC it was one call (checked 2026-09-20). The call has the same shape on chain 21 for a transaction of your own; with these chain-14 values a chain-20 node answered null (checked 2026-10-01), as a chain-21 node does, since neither ever held that transaction:
curl -s https://rpc.randprotocol.org -H 'content-type: application/json' -d '{
"jsonrpc": "2.0", "id": 1, "method": "rand_checkTransaction",
"params": ["e6ddaca3c852e6c861524a55d00a0fbdcaff6867544b9f19b9bf60ce263c32ed",
"7f68f57c4f33a3f0fe419f4d1a5df2875461339dbc454af8ced50cbd7f40e4ef"] }'
Never publish a viewing key of a wallet you use: it shows that wallet’s whole history, for good.
An ordinary address cannot create a bridged token. rand token create refuses a Bridge authority, and registering a bridged token, or adding a coin behind an existing one, needs a signed quorum of the bridge’s post-quantum guardian set. A token that names itself zUSD without that is just another token with a borrowed symbol, which is one more reason to check the rpl1… id. Bridged tokens always have 8 decimals on Rand.
Bridge covers deposits, redemptions and the guardian set.