Minima Blockchain — Complete Developer Reference
Extracted from Minima node v1.0.47.13 help system and developer documentation.
Covers: MDS API, KISS VM smart contracts, terminal commands, transaction construction.
Part 1: MDS (MiniDapp System) API & Node Commands
Minima MDS Developer Guide
You are a Minima blockchain developer specializing in MiniDapp development using MDS. When working on Minima projects:
Key Resources
- Documentation: https://docs.minima.global
- MDS Library:
@minima-global/mds(npm) - mds.js Source: https://github.com/minima-global/Minima/blob/master/mds/mds.js
- Build Portal: https://build.minima.global
Essential Setup
JavaScript (mds.js)
<script type="text/javascript" src="mds.js"></script>
TypeScript
npm install @minima-global/mds
import { MDS, MinimaEvents } from "@minima-global/mds";
React / TypeScript Project Setup
The official npm package @minima-global/mds (v0.14.11+) provides full TypeScript type definitions for the MDS API. It wraps the same API as mds.js — every pattern, command, and example in this document applies identically.
Install:
npm install @minima-global/mds
Source & Examples: https://github.com/minima-global/dev-tools
React MiniDapp scaffold (Vite):
npm create vite@latest my-minidapp -- --template react-ts
cd my-minidapp
npm install @minima-global/mds
Basic React hook pattern:
import { useEffect, useState } from "react";
import { MDS } from "@minima-global/mds";
function App() {
const [block, setBlock] = useState<string>("--");
const [balance, setBalance] = useState<string>("0");
useEffect(() => {
MDS.init((msg: any) => {
if (msg.event === "inited") {
MDS.cmd("block", (res: any) => {
setBlock(res.response.block);
});
MDS.cmd("balance", (res: any) => {
const minima = res.response.find((b: any) => b.tokenid === "0x00");
if (minima) setBalance(minima.confirmed);
});
}
if (msg.event === "NEWBLOCK") {
MDS.cmd("block", (res: any) => {
setBlock(res.response.block);
});
}
if (msg.event === "NEWBALANCE") {
MDS.cmd("balance", (res: any) => {
const minima = res.response.find((b: any) => b.tokenid === "0x00");
if (minima) setBalance(minima.confirmed);
});
}
});
}, []);
return (
<div>
<p>Block: {block}</p>
<p>Balance: {balance} Minima</p>
</div>
);
}
Key notes for React developers:
MDS.init()should be called once in a top-leveluseEffectwith an empty dependency array- MDS callbacks are NOT React state-aware — use refs or state setters, not stale closures
- The
service.jsbackground worker runs outside React — communicate viaMDS.comms.solo()(service→frontend) andMDS.comms.broadcast()(frontend→service) - For production builds, copy
mds.jsto yourpublic/folder for the debug connection, or rely on the MDS runtime injection when installed as a MiniDapp - The
dapp.conffile goes in your build output root alongsideindex.html - All SQL column names come back UPPERCASE in results regardless of your CREATE TABLE casing
Initialization Pattern
MDS.init(function(msg) {
if (msg.event == "inited") {
// MDS is ready - run commands here
MDS.cmd("balance", function(res) {
console.log(res);
});
}
});
Events to Handle
inited- MDS initializedNEWBLOCK- New block addedNEWBALANCE- User balance changedMINING- Mining eventMINIMALOG- Log messageMDSAPI- API message from another MiniDappMDSFAIL- Command failed
Common Commands
Get Balance
MDS.cmd("balance", function(res) {
// res.response is an array of token balances:
// { confirmed, unconfirmed, sendable, tokenid, token, coins, total }
const confirmed = res.response[0].confirmed;
const unconfirmed = res.response[0].unconfirmed;
// tokenid "0x00" = Minima; custom hex = other tokens
});
Get Address
MDS.cmd("getaddress", function(res) {
// res.response.address — hex format (0x...)
// res.response.miniaddress — Mx format (user-friendly)
// res.response.publickey
// res.response.script
// res.response.default
const miniaddress = res.response.miniaddress;
});
Send Transaction
// Send Minima
MDS.cmd("send address:<addr> amount:<amount>", callback);
// Send a specific token
MDS.cmd("send address:<addr> amount:<amount> tokenid:<tokenid>", callback);
Get Block Info
MDS.cmd("block", function(res) {
const blockNumber = res.response.block;
});
MDS.cmd("status", function(res) {
const chain = res.response.chain;
// chain.block — current block height
// chain.time — block timestamp
// chain.speed — block speed metric
// chain.difficulty
// chain.weight
// chain.hash
// chain.cascade.weight
});
List Coins
MDS.cmd("coins relevant:true", function(res) {
// Returns all UTXOs with coinid, amount, address, tokenid
});
Create Token
// Simple token
MDS.cmd("tokencreate name:MYTOKEN amount:1000 decimals:4", callback);
// Token with JSON metadata
MDS.cmd('tokencreate name:{"name":"POWER_1","color":"#FF0000"} amount:1 decimals:0', callback);
Manual UTXO Transaction Construction
// 1. Create transaction
MDS.cmd("txncreate id:mytxn", cb);
// 2. Add input coin
MDS.cmd("txninput id:mytxn coinid:0x<coinid>", cb);
// 3. Add outputs (inputs must equal outputs, or difference is burned)
MDS.cmd("txnoutput id:mytxn amount:10 address:Mx<recipient>", cb);
MDS.cmd("txnoutput id:mytxn amount:999999990 address:0x<change_address>", cb);
// 4. Sign
MDS.cmd("txnsign id:mytxn publickey:auto", cb);
// 5. Add proofs
MDS.cmd("txnbasics id:mytxn", cb);
// 6. Post
MDS.cmd("txnpost id:mytxn", cb);
Database (H2 SQL)
// Table creation — use backticks around names, auto_increment for IDs
MDS.sql(
"CREATE TABLE IF NOT EXISTS `metrics` (" +
" `id` bigint auto_increment," +
" `blockHash` varchar(160) NOT NULL," +
" `blockNumber` int NOT NULL," +
" `blockSpeed` float NOT NULL" +
")", cb
);
// Insert
MDS.sql("INSERT INTO metrics (blockHash, blockNumber) VALUES ('0xabc', 100)", cb);
// Query (res.rows is an array; column names are UPPERCASE in results)
MDS.sql("SELECT * FROM metrics ORDER BY blockNumber DESC LIMIT 10", function(res) {
if (res.status) {
res.rows.forEach(row => {
console.log(row.BLOCKNUMBER, row.BLOCKSPEED);
});
}
});
// Clip table to max rows (prevent unbounded growth)
MDS.sql(
"DELETE FROM metrics WHERE id NOT IN " +
"(SELECT id FROM metrics ORDER BY blockNumber DESC LIMIT 4320)", cb
);
File Operations
// Text / JSON
MDS.file.save("data.json", JSON.stringify(obj), cb);
MDS.file.load("data.json", function(res) { JSON.parse(res.response); });
// Binary (HEX-encoded)
MDS.file.savebinary("file.bin", hexString, cb);
MDS.file.loadbinary("file.bin", function(res) { const hex = res.response; });
// Management
MDS.file.delete("file.json", cb);
MDS.file.copy("src.json", "dest.json", cb);
MDS.file.move("old.json", "new.json", cb);
MDS.file.makedir("myfolder", cb);
MDS.file.list("/", cb);
MDS.file.getpath("file.json", cb);
// Web folder (serve files from MiniDapp web root)
MDS.file.listweb("/", cb);
MDS.file.copytoweb("localfile.js", "web/file.js", cb);
MDS.file.deletefromweb("web/file.js", cb);
// Upload / Download (upload chunks at 1MB)
MDS.file.upload(fileObject, cb);
MDS.file.download(url, cb);
Network Requests
MDS.net.GET("https://api.example.com/data", cb);
MDS.net.POST("https://api.example.com/post", "data=value", cb);
Key-Value Storage
MDS.keypair.set("username", "alice", cb);
MDS.keypair.get("username", cb);
Inter-MiniDapp Communication
// Broadcast to ALL MiniDapps
MDS.comms.broadcast("Hello all MiniDapps!", cb);
// Private message to your own service.js only
MDS.comms.solo("Private message", cb);
// Direct API call to another MiniDapp
MDS.api.call("OtherDappName", data, function(reply) {
console.log(reply.status, reply.data);
});
MDS.api.reply("OtherDappName", requestId, responseData, cb);
// Get link/URL to another MiniDapp
MDS.dapplink("OtherDappName", function(res) {
if (res.status) window.location = res.base;
});
Notifications
MDS.notify("Transaction sent!");
MDS.notifycancel();
Utility Functions
MDS.util.hexToBase64(hexstring); // remove 0x prefix first
MDS.util.base64ToHex(base64String);
MDS.util.base64ToArrayBuffer(base64String);
MDS.util.getStateVariable(coin, port); // read state var from a coin
Form / URL Param Parsing
const value = MDS.form.getParams("paramName"); // returns value or null
Logging
MDS.log("message"); // outputs: "Minima @ [timestamp] : message"
MiniDapp Structure
myminidapp/
├── dapp.conf # Config (name, version, description)
├── favicon.ico # Icon
├── index.html # Main page
├── service.js # Background service (optional, runs persistently)
├── styles.css # Styles
└── mds.js # MDS library (copy from Minima repo)
dapp.conf Format
{
"name": "MyMiniDapp",
"version": "1.0.0",
"description": "What this MiniDapp does",
"icon": "favicon.ico"
}
service.js Pattern (Background Processing)
service.js runs persistently in the background. Use it for data collection and event processing.
// Load shared logic from another JS file
MDS.load("mylogic.js");
MDS.init(function(msg) {
if (msg.event === "inited") {
// One-time setup: create tables, load state
createMetricsTable(function() {
MDS.log("Service ready");
});
}
else if (msg.event === "NEWBLOCK") {
// Process every new block
saveNewBlock(msg);
}
});
Hello World MiniDapp (Minimal Example)
<html>
<head>
<script type="text/javascript" src="mds.js"></script>
</head>
<body>
<p>Block: <span id="block"></span></p>
<script>
MDS.init(function(msg) {
if (msg.event === "inited" || msg.event === "NEWBLOCK") {
MDS.cmd("block", function(res) {
document.getElementById("block").innerText = res.response.block;
});
}
});
</script>
</body>
</html>
Metrics / Dashboard Pattern
// Collect block metrics on each NEWBLOCK, store in SQL, display with Chart.js
MDS.init(function(msg) {
if (msg.event === "inited") {
createTable(() => loadAndDisplayChart());
}
else if (msg.event === "NEWBLOCK") {
MDS.cmd("status", function(res) {
const c = res.response.chain;
MDS.sql(
`INSERT INTO metrics (blockNumber, blockSpeed, blockHash) ` +
`VALUES (${c.block}, ${c.speed}, '${c.hash}')`, cb
);
});
}
});
// Query for chart
MDS.sql("SELECT * FROM metrics ORDER BY blockNumber DESC LIMIT 10", function(res) {
if (res.status && res.rows.length >= 10) {
const labels = res.rows.map(r => r.BLOCKNUMBER).reverse();
const data = res.rows.map(r => (1 / parseFloat(r.BLOCKSPEED)).toFixed(1)).reverse();
// pass labels + data to Chart.js
}
});
Packaging & Installation
zip -r mydapp.mds.zip *
# Install via MiniHub UI at https://127.0.0.1:9003
# Or: mds action:install file:/path/to/mydapp.mds.zip
Debug Mode
Set in mds.js for local development:
DEBUG_HOST: "127.0.0.1",
DEBUG_PORT: 9003,
DEBUG_MINIDAPPID: "your-uid-from-url"
Extracting Values from Command Responses
Balance (array — strip JSON quotes with slice)
MDS.cmd("balance", function(res) {
const confirmed = JSON.stringify(res.response[0].confirmed).slice(1, -1);
const unconfirmed = JSON.stringify(res.response[0].unconfirmed).slice(1, -1);
});
Address (Mx format)
MDS.cmd("getaddress", function(res) {
const addr = JSON.stringify(res.response.miniaddress).slice(1, -1);
});
NEWBLOCK — get block number from event data
MDS.init(function(msg) {
if (msg.event === "NEWBLOCK") {
const blockNumber = parseInt(msg.data.txpow.header.block);
}
});
Status — all useful chain fields
MDS.cmd("status", function(msg) {
const c = msg.response.chain;
// c.block, c.time, c.speed, c.difficulty, c.weight, c.hash, c.cascade.weight
const totalWeight = msg.response.weight; // total (chain + cascade)
const seconds = (1 / c.speed).toFixed(1); // convert speed → seconds/block
});
Send — check for error
MDS.cmd(`send address:${addr} amount:${amt}`, function(res) {
if (res.error) {
document.getElementById("status").innerText = res.error;
} else {
document.getElementById("status").innerText = "Sent!";
}
});
Complete Wallet MiniDapp Pattern
function GetBalance() {
MDS.cmd("balance", function(res) {
document.getElementById("balance").innerText =
JSON.stringify(res.response[0].confirmed).slice(1, -1);
document.getElementById("unconfirmed").innerText =
JSON.stringify(res.response[0].unconfirmed).slice(1, -1);
});
}
function GetAddress() {
MDS.cmd("getaddress", function(res) {
document.getElementById("address").innerText =
JSON.stringify(res.response.miniaddress).slice(1, -1);
});
}
function Send() {
const addr = document.getElementById("sendAddress").value;
const amt = document.getElementById("amount").value;
MDS.cmd(`send address: ${addr} amount: ${amt}`, function(res) {
document.getElementById("status").innerText =
res.error ? res.error : "Something went wrong, please try again.";
});
}
MDS.init(function(msg) {
if (msg.event === "inited") {
GetBalance();
GetAddress();
}
if (msg.event === "NEWBALANCE") {
GetBalance();
}
});
Complete Metrics Dashboard Pattern (MiniStats)
service.js — background data collection
MDS.load("ministats.js"); // load shared functions
MDS.init(function(msg) {
if (msg.event == "inited") {
createMetricsTable(function() { MDS.log("ready"); });
} else if (msg.event == "NEWBLOCK") {
saveNewBlock(msg);
}
});
ministats.js — shared logic
function saveNewBlock(msg) {
const blockNumber = parseInt(msg.data.txpow.header.block);
isBlockSaved(blockNumber, function(saved) {
if (!saved) buildMetricRow(blockNumber);
});
}
function buildMetricRow(blockNumber) {
MDS.cmd("status", function(msg) {
const c = msg.response.chain;
storeMetric({
time: c.time, blockHash: c.hash, blockSpeed: c.speed,
chainweight: c.weight, difficulty: c.difficulty,
cascadeWeight: c.cascade.weight, blockNumber
});
});
}
function storeMetric(m) {
const MAX = 4320; // ~1 day at 20s/block
MDS.sql(
`INSERT INTO metrics (blockTime,blockHash,cascadeWeight,blockSpeed,chainWeight,blockDifficulty,blockNumber) ` +
`VALUES ('${m.time}','${m.blockHash}',${m.cascadeWeight},${m.blockSpeed},'${m.chainweight}','${m.difficulty}',${m.blockNumber})`,
function(res) { MDS.log(res.status ? "saved" : "failed"); }
);
MDS.sql(
`DELETE FROM METRICS WHERE id NOT IN (SELECT id FROM METRICS ORDER BY blockNumber DESC LIMIT ${MAX})`,
function() {}
);
}
function isBlockSaved(blockNumber, callback) {
MDS.sql(`SELECT * FROM metrics WHERE blockNumber = ${blockNumber}`, function(res) {
callback(res.status && res.rows.length > 0);
});
}
function createMetricsTable(callback) {
MDS.sql(
"CREATE TABLE IF NOT EXISTS `metrics` (" +
" `id` bigint auto_increment," +
" `blockTime` varchar(160) NOT NULL," +
" `blockHash` varchar(160) NOT NULL," +
" `blockDifficulty` varchar(160) NOT NULL," +
" `blockNumber` int NOT NULL," +
" `blockSpeed` float NOT NULL," +
" `chainWeight` varchar(160) NOT NULL," +
" `cascadeWeight` varchar(160) NOT NULL)",
function(res) { if (callback) callback(res); }
);
}
function deleteTable() {
MDS.sql("DROP TABLE metrics", function(res) {
MDS.log(res.status ? "deleted" : "failed");
});
}
index.html — display + charts (Chart.js 2.9.4)
MDS.init(function(msg) {
if (msg.event === "inited") {
displayMetrics();
displayCharts();
} else if (msg.event === "NEWBLOCK") {
displayMetrics();
window.location.reload(); // refresh charts
}
});
function displayMetrics() {
MDS.cmd("status", function(msg) {
const c = msg.response.chain;
document.getElementById("current-block").innerText = c.block;
document.getElementById("block-speed").innerText = (1/c.speed).toFixed(1) + " s/block";
document.getElementById("block-difficulty").innerText = c.difficulty;
document.getElementById("chain-weight").innerText = c.weight;
document.getElementById("cascade-weight").innerText = c.cascade.weight;
document.getElementById("current-block-time").innerText = c.time;
document.getElementById("block-hash").innerText = c.hash;
document.getElementById("total-weight").innerText = msg.response.weight;
});
}
// Chart helper — query N blocks, display avg speed + line chart
function showChart(limit, canvasId, labelId) {
MDS.sql(`SELECT * FROM metrics ORDER BY blockNumber DESC LIMIT ${limit}`, function(res) {
if (!res.status) return;
const rows = res.rows;
if (rows.length < limit) {
document.getElementById(labelId).innerText = `N/A (less than ${limit} blocks)`;
document.getElementById(canvasId).style.display = "none";
return;
}
let totalSpeed = 0;
const xdata = [], ydata = [];
rows.forEach(row => {
totalSpeed += parseFloat(row.BLOCKSPEED);
xdata.push(row.BLOCKNUMBER);
ydata.push(parseFloat(1 / row.BLOCKSPEED));
});
document.getElementById(labelId).innerText =
(1 / (totalSpeed / rows.length)).toFixed(1) + " s/block";
new Chart(canvasId, {
type: "line",
data: {
labels: xdata.reverse(),
datasets: [{ fill: false, lineTension: 0,
backgroundColor: "rgba(0,0,255,1.0)",
borderColor: "rgba(0,0,255,0.1)",
data: ydata.reverse() }]
},
options: {
legend: { display: false },
scales: { yAxes: [{ ticks: {
callback: v => v.toFixed(1) + " s/block"
}}]}
}
});
});
}
function displayCharts() {
showChart(10, "myChart", "previous-ten");
showChart(100, "myChart2", "previous-hundred");
showChart(1000, "myChart3", "previous-thousand");
}
Best Practices
1. Always initialize MDS before running commands
2. Handle inited, NEWBLOCK, NEWBALANCE, MDSFAIL in init callback
3. Use SQL for persistent multi-field data; keypair for simple key-value settings
4. Cap SQL tables with a DELETE … NOT IN (… LIMIT N) clip to prevent unbounded growth
5. Use service.js + MDS.load() for background block processing
6. Check res.error on send commands; check res.status on SQL commands
7. Column names in SQL results come back UPPERCASE (row.BLOCKSPEED, not row.blockSpeed)
8. Use window.location.reload() on NEWBLOCK to refresh chart data in index.html
9. Test on local node before packaging — set DEBUG_HOST/PORT/MINIDAPPID in mds.js
Common Issues
- CORS errors: External HTTP requests may be blocked
- Balance shows 0: Check
unconfirmedfield — UTXO may be locked pending confirmation - Commands fail: Verify MDS password is set on the node
- SQL errors: H2 has minor MySQL differences — use backtick-quoted identifiers
- Column names: SQL result columns are always UPPERCASE regardless of CREATE TABLE casing
- Double-spend: Cannot spend a coin until change from previous tx is confirmed (UTXO lock)
block
block
Return the top block
Examples:
block
keys
keys
Get a list of all your public keys or create a new key.
Each public key can be used for signing securely 262144 (64^3) times.
action: (optional)
list : List your existing public keys. The default.
checkkeys : Checks if your Public and Private keys are correct.
new : Create a new key pair that is stored and managed in your DB.
genkey : Generate a new key NOT stored in the DB.
publickey: (optional)
Search for a specific public key.
Examples:
keys
keys action:list
keys action:genkey
keys action:checkkeys
keys action:list publickey:0xFFEE56..
keys action:new
newaddress
newaddress
Create a new address that will not be not used for anything else (not one of the 64 default change address).
Can be used for a specific use case or for improved privacy.
Examples:
newaddress
maxima
maxima
Check your Maxima details, send a message / data.
Maxima is an information transport layer running on top of Minima.
action:
info : Show your Maxima details - name, publickey, staticmls, mls, local identity and contact address.
setname : Set your Maxima name so your contacts recognise you. Default 'noname'.
seticon : Set your Maxima name Icon.
hosts : List your Maxima hosts and see their Maxima public key, contact address, last seen time and if you are connected.
| to |
|---|
sendall : Send a message to ALL your contacts. Must specify 'application' and 'data' parameters.
refresh : Refresh your contacts by sending them a network message.
name: (optional)
Set your name. Use with 'action:setname'.
icon: (optional)
Set your icon. Use with 'action:seticon'.
| to |
|---|
The id, contact address or public key of the recipient of the message. Use with 'action:send'.
application: (optional)
A string that identifies which application should process the message. Use with 'action:send'.
data: (optional)
The data to send. Can be HEX or a JSON object. Use with 'action:send'.
poll: (optional)
true or false, true will poll the send action until successful. Use with 'action:send'.
delay: (optional)
Only used with poll or with sendall. Delay sending the message by this many milliseconds
Examples:
maxima action:info
maxima action:setname name:myname
maxima action:hosts
maxima action:send id:1 application:appname data:0xFED5..
maxima action:send to:MxG18H.. application:appname data:0xFED5..
maxima action:send publickey:0xCD34.. application:appname data:0xFED5.. poll:true
maxima action:refresh
maxextra
maxextra
Perform extra functions on Maxima.
Set your static mls host. Your mls shares your changing contact address with your existing contacts to ensure you remain connected.
To permanently allow incoming messages over Maxima from non-contacts, you can allow 'getaddress' requests for your current contact address.
'getaddress' requests require your permanent maxaddress (MAX#yourpubkey#yourmls) and are requested via your static mls.
To set this up, on your static mls node, your Maxima public key must be added as permanent.
To prevent public users adding you as a contact, you can disable adding new contacts.
Optionally, you can explicitly allow specific Maxima public keys who can add you as a contact.
action:
staticmls : Set an unchanging Maxima Location Service (mls) host for yourself.
addpermanent : On your static mls node, add your Maxima public key to allow 'getaddress' requests from anyone.
removepermanent : On your static mls node, remove your Maxima public key to stop allowing 'getaddress' requests.
listpermanent : On your static mls node, list all public keys currently allowing public requests for their contact address.
clearpermanent : On your static mls node, remove ALL public keys currently allowing requests for their contact address.
getaddress : Request the current contact address of a permanently accessible user from their static mls host.
mlsinfo : List info about users using you as their mls and the public keys of their contacts.
allowallcontacts : If you have shared your permanent maxaddress, you can disable/enable users adding you as a contact.
addallowed : If 'allowallcontacts' is disabled, you can authorise specific users to add you as a contact. Stored in RAM.
listallowed : List all the public keys which are allowed to add you as a Maxima contact.
clearallowed : Remove the public keys of ALL users which are allowed to add you as a Maxima contact.
publickey: (optional)
The Maxima public key of the user who wants to share their permanent maxaddress to be publicly contactable over Maxima.
Or the Maxima public key of a user who is allowed to add you as a contact.
maxaddress: (optional)
Used with 'action:getaddress' to get the contact address of a user using their permanent maxaddress.
Their maxaddress must be in the format MAX#pubkey#staticmls, with their public key and static mls host address.
enable: (optional)
true or false, use with 'action:allowallcontacts' to enable or disable all new contacts.
host: (optional)
The 'p2pidentity' of a server node which is always online.
Use with 'action:staticmls' to set the host of your static mls.
Examples:
maxextra action:staticmls host:Mx...@34.190.784.3:9001
maxextra action:staticmls host:clear
maxextra action:addpermanent publickey:0x3081..
maxextra action:removepermanent publickey:0x3081..
maxextra action:listpermanent
maxextra action:clearpermanent
maxextra action:getaddress maxaddress:MAX#0x3081..#Mx..@34.190.784.3:9001
maxextra action:mlsinfo
maxextra action:allowallcontacts enable:false
maxextra action:addallowed publickey:0x2451..
maxextra action:listallowed
maxextra action:clearallowed
maxsign
maxsign
Sign a piece of data with your Maxima ID.
Returns the signature of the data, signed with your Maxima private key or the specified key.
data:
The 0x HEX data to sign.
privatekey: (optional)
The 0x HEX data of the private key from maxcreate. Uses your Maxima ID otherwise.
Examples:
maxsign data:0xCD34..
maxsign data:0xCD34.. privatekey:0x30819..
message
message
Send a message to one or all of your direct peers.
data:
The message as a string.
uid: (optional)
Leave blank to send a message to all peers or enter the uid of the peer to send the message to.
uid can be found from the 'network' command.
Examples:
message data:"hello" uid:CVNPMLPOCQ0HQ
disconnect
disconnect
Disconnect from a connected or connecting host.
Optionally disconnect from all hosts.
uid:
Use 'all' to disconnect from all hosts or enter the uid of the host to disconnect from.
uid can be found from the 'network' command.
Examples:
disconnect uid:CVNPMLPOCQ0HQ
disconnect uid:all
webhooks
webhooks
Add a web hook that is called with Minima events as they happen.
POST requests, so the URL must be a POST endpoint.
action: (optional)
list : List your existing webhooks. The default.
add : Add a new webhook.
remove : Remove an existing webhook.
clear : Clear the existing webhooks.
hook: (optional)
A URL, must be a POST endpoint.
filter: (optional)
Filters which events get posted.
Examples:
webhooks action:list
webhooks action:add hook:http://127.0.0.1/myapi.php
webhooks action:remove hook:http://127.0.0.1/myapi.php
webhooks action:add hook:http://127.0.0.1/myapi.php filter:MINING
webhooks action:clear
checkpending
checkpending
Show if a pending transaction UID is in the pending list
Examples:
checkpending uid:0xFF..
checkrestore
checkrestore
Check if Minima is restoring
Examples:
checkrestore
restore
restore
Restore your node from a backup. You MUST wait until all your original keys are created before this is allowed.
file:
Specify the filename or local path of the backup to restore
password: (optional)
Enter the password of the backup
Examples:
restore file:my-full-backup-01-Jan-22 password:Longsecurepassword456
archive
archive
Perform a chain or seed re-sync from an archive node or archive export file.
A chain re-sync will put your node on the correct chain.
Use a chain re-sync if your node has been offline for too long and cannot catchup. Seed Phrase is not required.
A seed re-sync will wipe the wallet, re-generate your private keys and restore your coins.
Only do a seed re-sync if you have lost your node and do not have a backup.
You can also perform checks on your archive db or archive file and check an address.
action:
integrity : Check the integrity of your archive db. No host required.
inspect : inspect an archive export .gzip file. If 'last:1', the file can re-sync any node from genesis.
export : Export your archive db to a .gzip file.
exportraw : Export your archive db to a raw .dat file (recommended).
resync : Use with 'host' to do a chain or seed re-sync from an archive node.
import : Use with 'file' to do a chain or seed re-sync using a .dat or .gzip archive file.
addresscheck : check your archive db for spent and unspent coins at a specific address.
host: (optional)
ip:port of the archive node to sync from. Use with action:resync.
file: (optional)
name or path of the archive export gzip file to export/import/inspect.
phrase: (optional)
To seed re-sync, enter your seed phrase in double quotes. Use with action:import or resync.
This will replace the current seed phrase of this node. You do NOT have to do this if you still have access to your wallet.
In this case, use action:import or resync without 'phrase' to get on the correct chain.
anyphrase: (optional)
true or false. If you set a custom seed phrase on startup, you can set this to true. Default is false.
keys: (optional)
Number of keys to create if you need to do a seed re-sync. Default is 64.
keyuses: (optional)
How many times at most you used your keys..
Every time you re-sync with seed phrase this needs to be higher as Minima Signatures are stateful.
Defaults to 1000 - the max is 262144 for normal keys.
address: (optional)
The wallet or script address to search for in the archive. Use with action:addresscheck.
If using a script address, also provide an address or public key in the 'statecheck' parameter.
Use with action:addresscheck.
statecheck: (optional)
Data to search for in a coin's state variables.
Combine with a script address in the 'address' parameter to search for coins locked in a contract.
logs: (optional)
true or false. Show detailed logs, default false.
maxexport: (optional)
How many blocks to export to a raw .dat file. Useful for testing purposes.
Examples:
archive action:integrity
archive action:inspect file:archiveexport-ddmmyy.gzip
archive action:exportraw file:archiveexport-ddmmyy.raw.dat
archive action:resync host:89.98.89.98:9001
archive action:import file:archiveexport-ddmmyy.raw.dat
archive action:import file:archiveexport-ddmmyy.raw.dat phrase:"YOUR 24 WORD SEED PHRASE" keyuses:2000
archive action:addresscheck address:0xFED.. statecheck:0xABC..
megammr
megammr
View information about your MegaMMR. Export and Import complete MegaMMR data.
You must be running -megammr.
action: (optional)
info : Shows info about your MegaMMR.
export : Export a MegaMMR data file.
import : Import a MegaMMR data file.
file: (optional)
Use with export and import.
Examples:
megammr
megammr action:export
megammr action:export file:thefile
megammr action:import file:thefile
Part 2: KISS VM Smart Contracts
Minima Smart Contracts (KISS VM)
You are a Minima blockchain developer specializing in smart contracts using the KISS (Keep It Simple Stupid) VM.
CRITICAL RULES (proven on-chain 2026-03-31)
1. NO underscores in variable names. player_pick silently fails. Use playerpick, hpk, pa.
2. Script size limit ~1200 characters. Use 2-3 char variable names. Use MAST for rarely-used paths.
3. VERIFYOUT keepstate rules:
- Phase transitions (coin stays at script address): VERIFYOUT(... TRUE) + txnoutput storestate:true
- Final payouts (to wallet address): VERIFYOUT(... FALSE) + txnoutput storestate:false
4. Use @COINAGE not @BLKNUM for timing. @BLKNUM is broken on synced nodes. Store timeout duration in state, check @COINAGE GT timeout.
5. XOR/AND/OR/NAND/NOR are BOOLEAN operators (TRUE/FALSE), NOT bitwise.
6. NUMBER() fails on 32-byte hex. Use SUBSET(0 4 hash) to truncate before NUMBER().
7. Commit-reveal randomness: LET h=SHA3(CONCAT(secret1 secret2)) LET r=NUMBER(SUBSET(0 4 h))%range
Language Reference
Variables & Assignment
LET myvar = 5
LET hpk = PREVSTATE(0)
Control Flow
IF condition THEN ... ENDIF
IF condition THEN ... ELSEIF condition THEN ... ELSE ... ENDIF
WHILE condition DO ... ENDWHILE
ASSERT expression /* fails script if false */
RETURN TRUE/FALSE /* must return boolean */
State Functions
STATE(port)— read from the TRANSACTION state (set by txnstate)PREVSTATE(port)— read from the INPUT COIN state (set when coin was created)SAMESTATE(start end)— assert STATE(start..end) == PREVSTATE(start..end). Non-contiguous needs multiple calls:SAMESTATE(0 5)+SAMESTATE(7 11)
Signature Functions
SIGNEDBY(publickey)— returns TRUE if tx signed by this pubkeyMULTISIG(threshold pk1 pk2 ...)— multi-signature check
Output/Input Verification
VERIFYOUT(outputindex address amount tokenid keepstate)— verify output matches. keepstate TRUE = output state must match input state. keepstate FALSE = output must NOT store state.VERIFYIN(inputindex address amount tokenid)— verify input matchesGETOUTADDR(index),GETOUTAMT(index),GETOUTTOK(index),GETOUTKEEPSTATE(index)GETINADDR(index),GETINAMT(index),GETINTOK(index),GETINID(index)SUMOUTPUTS(tokenid),SUMINPUTS(tokenid)
Cryptography
SHA3(data)— Keccak-256 hash, returns 32-byte hexCONCAT(a b)— concatenate hex values:CONCAT(0xAA 0xBB)→0xAABBSUBSET(start length data)— extract bytes from hexNUMBER(hex)— convert small hex to number (max ~4 bytes)
Global Variables
@INPUT— index of current input being validated@AMOUNT— amount of current input coin@ADDRESS— address (script hash) of current input coin@TOKENID— token ID of current input coin@COINID— coin ID of current input@COINAGE— blocks since coin was created (USE THIS FOR TIMING)@BLKNUM— current block number (BROKEN on synced nodes, avoid)@TOTIN— total number of inputs@TOTOUT— total number of outputs@FLOATING— if this is a floating transaction@SCRIPT— the script itself as text
Comparison Operators
EQ NEQ GT GTE LT LTE
Boolean Operators (NOT bitwise!)
AND OR XOR NAND NOR NXOR — operate on TRUE/FALSE only
Arithmetic
+ - * / % — MiniNumber arbitrary precision
Limits
- 1024 instructions maximum per script execution
- ~1200 characters maximum script size (undocumented, silently fails)
- 64 stack depth
- 32 function params maximum
- 256 state ports (0-255)
Proven Multi-Phase Contract Pattern
The Universal Casino (v0.7, proven on-chain) demonstrates the definitive pattern:
State Layout
0=housepk 1=houseaddr 2=housecommit 3=range 4=payout 5=bet
6=phase 7=timeout 8=playerpk 9=playeraddr 10=playercommit
11=playerpick 12=housesecret 13=playersecret
Script (1124 chars, minified)
LET hpk=PREVSTATE(0) LET ha=PREVSTATE(1) LET hc=PREVSTATE(2) LET rng=PREVSTATE(3)
LET po=PREVSTATE(4) LET bt=PREVSTATE(5) LET ph=PREVSTATE(6) LET to=PREVSTATE(7)
/* A: Owner cancel */
IF ph EQ 0 AND SIGNEDBY(hpk) THEN RETURN TRUE ENDIF
/* B: Take — recreate coin at phase 1 */
IF ph EQ 0 THEN
ASSERT SAMESTATE(0 5) ASSERT STATE(6) EQ 1 ASSERT STATE(7) EQ to
LET pp=STATE(11) ASSERT pp GTE 0 AND pp LT rng
LET tt=@AMOUNT+bt
ASSERT VERIFYOUT(@INPUT @ADDRESS tt @TOKENID TRUE)
RETURN TRUE
ENDIF
LET ppk=PREVSTATE(8) LET pa=PREVSTATE(9) LET pc=PREVSTATE(10) LET pk=PREVSTATE(11)
/* C: Reveal — recreate at phase 2 */
IF ph EQ 1 AND SIGNEDBY(hpk) THEN
ASSERT SAMESTATE(0 5) ASSERT STATE(6) EQ 2 ASSERT SAMESTATE(7 11)
LET hs=STATE(12) ASSERT SHA3(hs) EQ hc
ASSERT VERIFYOUT(@INPUT @ADDRESS @AMOUNT @TOKENID TRUE)
RETURN TRUE
ENDIF
/* D: Resolve — determine winner, pay out */
IF ph EQ 2 AND SIGNEDBY(ppk) THEN
LET ps=STATE(13) ASSERT SHA3(ps) EQ pc
LET hs=PREVSTATE(12)
LET h=SHA3(CONCAT(hs ps)) LET r=NUMBER(SUBSET(0 4 h))%rng
IF r EQ pk THEN
LET w=bt*po
ASSERT VERIFYOUT(@INPUT pa w @TOKENID FALSE)
IF @AMOUNT GT w THEN ASSERT VERIFYOUT(@INPUT+1 ha @AMOUNT-w @TOKENID FALSE) ENDIF
ELSE
ASSERT VERIFYOUT(@INPUT ha @AMOUNT @TOKENID FALSE)
ENDIF
RETURN TRUE
ENDIF
RETURN FALSE
Transaction Construction for Each Path
Create (send command):
send amount:<stake> address:<scriptAddr> state:{"0":"<pk>","1":"<addr>","2":"<commit>","3":"<range>","4":"<payout>","5":"<bet>","6":"0","7":"<timeout>"}
Take (Path B) — txn construction:
txncreate → txninput(casino coin) → txninput(funding coin) →
txnoutput(total, scriptAddr, storestate:true) → txnoutput(change, walletAddr, storestate:false) →
txnstate(ports 0-11: preserve 0-5, set 6=1, 7=timeout, 8-11=player info) →
txnsign(auto) → txnbasics → txnpost
Reveal (Path C) — txn construction:
txncreate → txninput(phase-1 coin) →
txnoutput(same amount, scriptAddr, storestate:true) →
txnstate(preserve 0-5, set 6=2, preserve 7-11, add 12=secret) →
txnsign(housepk) → txnbasics → txnpost
Resolve (Path D) — txn construction:
txncreate → txninput(phase-2 coin) →
txnoutput(winnings to winner, storestate:false) → [optional: txnoutput(remainder to loser)] →
txnstate(port 13 = player secret) →
txnsign(playerpk) → txnbasics → txnpost
Testing
runscript script:"..." state:{} prevstate:{} globals:{} signatures:[]— test offline, but cannot test VERIFYOUT/SAMESTATE (no tx context)txncheck id:— validates structure but NOT @BLKNUM/@COINAGE globalsnewscript script:"..." trackall:true— register on node, returns address- Post small test transactions on private nodes to verify on-chain behavior
- Track coinids through phase transitions, not phases (multiple games share the address)
Common Mistakes
| Mistake | Result | Fix |
|---|---|---|
| Underscore in variable name | Silent parse error at runtime | Use camelCase/flat |
| VERIFYOUT FALSE + storestate:true | Tx silently rejected | Use TRUE for phase transitions |
| @BLKNUM for deadlines | Tx rejected on synced nodes | Use @COINAGE |
| XOR for bitwise operations | Script fails | SHA3(CONCAT()) instead |
| NUMBER(SHA3(x)) | Overflow, script fails | SUBSET(0 4 hash) first |
| Script > 1200 chars | Silently rejected on-chain | Minify variables, use MAST |
| SAMESTATE(0 11) when port 6 changes | SAMESTATE fails | Split: SAMESTATE(0 5) + SAMESTATE(7 11) |
random
random
Generate a random hash value, defaults to 32 bytes.
size: (optional)
Integer number of bytes for the hash value.
type: (optional)
sha3 (default) or sha2.
Examples:
random
random size:64
maths
maths
Run some maths with Minima MiniNUmber precision
Returns the result - na d full logs if required.
calculate:
The maths you want calculated
logs: (boolean)
true or false if you want the logs.
Examples:
maths calculate:"1+2 * (3/4)"
maths calculate:"1+2 * SIGDIG(1 3/4)" logs:true
newscript
newscript
Add a new custom script.
Track ALL coins with this script address or just ones with state variables relevant to you.
script:
The script to add to your node.
Your node will then know about coins with this script address.
trackall:
true or false, true will track all coins with this script address.
false will only track coins with this script address that are relevant to you.
clean: (optional)
true or false, true will clean the script to its minimal correct representation.
Default is false.
Examples:
newscript trackall:true script:"RETURN SIGNEDBY(0x1539..) AND SIGNEDBY(0xAD25..)"
newscript trackall:false script:"RETURN (@BLOCK GTE PREVSTATE(1) OR @COINAGE GTE PREVSTATE(4)) AND VERIFYOUT(@INPUT PREVSTATE(2) @AMOUNT @TOKENID FALSE)"
removescript
emovescript
Remove a custom script. BE CAREFUL not to remove a script you need.
address:
The address of the script.
Examples:
removescript address:0xFFE678768CDE..
removescript address:MxFFE678768CDE..
mmrcreate
mmrcreate
Create an MMR Tree of data. Can be used in MAST contracts.
Must specify a JSON array of string/HEX data for the leaf nodes.
They could be a list of public keys.. and then in a script you can check if the given key is one in the set.
OR you can have different scripts.. and then you can execute any number of scripts from the same UTXO.
Returns the MMR data and proof for each leaf node and the MMR root hash.
nodes:
JSON array of string/HEX data for the leaf nodes.
Examples:
mmrcreate nodes:["RETURN TRUE","RETURN FALSE"]
mmrcreate nodes:["0xFF..","0xEE.."]
sign
sign
Sign the data with the publickey.
Returns the signature of the data, signed with the corresponding private key.
data:
The 0x HEX data to sign.
Examples:
sign data:0xCD34..
Part 3: Terminal Transactions & UTXO Model
Minima Transaction Tutorials
You are a Minima blockchain developer specializing in terminal transactions and the UTXO model. Use these commands when working with Minima nodes via terminal.
Key Concepts
UTXO Model
Minima uses the Unspent Transaction Output (UTXO) model. Key points:
- Every transaction consumes inputs and creates outputs
- Inputs must equal outputs (difference is burned)
- Sending coins twice without waiting for confirmation will fail (no funds)
- Change is returned to a new address automatically
Token ID
0x00= Minima (base currency)- Custom hex
0xFF...= User-created tokens
Basic Commands
Get Wallet Address
getaddress # Returns the current default address
newaddress # Generates a new address (each node has 64 default addresses)
Returns: {"address": "0x...", "miniaddress": "Mx...", "publickey": "...", "script": "...", "default": true}
Both 0x... (hex) and Mx... (miniaddress) formats can be used interchangeably when sending.
Check Balance
balance
Returns all token balances with:
confirmed— confirmed coinsunconfirmed— pending coins (UTXO locked, can't spend yet)sendable— coins ready to sendtokenid—0x00= Minima, custom hex = user tokenstoken— token metadata object (name, color, etc.)total— total supply for tokens
Send Minima
send address:<recipient> amount:<amount>
amountcan go up to 44 decimals- Minimum: 0.000001
Send Tokens
send address:<recipient> amount:<amount> tokenid:<tokenid>
Requires the tokenid from the balance response.
Enable RPC (Mobile nodes)
rpc enable:true
# Enables RPC interface at port 9002, allowing remote commands via HTTP
Token Creation
Simple Token
tokencreate name:<TOKENNAME> amount:<amount> decimals:<decimals>
- Consumes a small fraction of Minima as input (converted to token scale)
- Change is returned automatically in a second output
Token with JSON Metadata
tokencreate name:{"name":"MYTOKEN","color":"#FF0000","size":2} amount:1 decimals:0
JSON allows arbitrary metadata — any properties you define are stored on-chain.
Common metadata fields (application-defined, not enforced by protocol):
"name"— display name"color"— RGB hex color e.g."#FF0000""size"— numeric scale"icon"— URL or IPFS link to image
Token Creation Response Structure
inputs[0].tokenid = "0x00" ← Minima used as input
outputs[0].tokenid = "0xFF" ← new token output (scale representation)
outputs[0].token.tokenid ← real tokenid (use this for send/balance)
outputs[0].token.name ← your metadata object
outputs[0].token.total ← total supply
outputs[0].token.decimals ← decimal places
outputs[0].token.script ← default "RETURN TRUE"
outputs[0].tokenamount ← human-readable supply
outputs[1].tokenid = "0x00" ← change back to sender in Minima
Check Token After Creation
balance
# Token appears with full metadata and its tokenid
# "unconfirmed" until mined into a block
Transaction Construction (Manual UTXO)
CRITICAL RULES (proven on-chain)
- 3-step post: txnsign → txnbasics → txnpost. Sign BEFORE basics. NEVER combine.
- NEVER use
txnpost auto:truefor script-address coins — returns status:true but silently fails on-chain. - NEVER combine
scriptmmr:truewithtxnbasics— duplicate MMR proof error. txnpostreturns status:true even for invalid transactions — the tx broadcasts but gets rejected. Cannot trust status alone.txncheckcannot validate @BLKNUM/@COINAGE — always shows scripts:false for those globals. Don't gate on txncheck.- Always
txndeleteon error paths aftertxncreate— otherwise input coins get locked. storestate:truefor phase-transition outputs (coin stays at script address).storestate:falsefor payout outputs.- Use
send ... split:Nto split a UTXO into N equal outputs in one transaction.
For complex transactions requiring precise coin control:
1. Create Transaction
txncreate id:<txnid>
2. Add Input Coin
txninput id:<txnid> coinid:<coinid>
Get coin IDs from: coins relevant:true
Shortcut — add coin AND its MMR proof/script in one step:
txninput id:<txnid> coinid:<coinid> scriptmmr:true
3. Add Outputs
txnoutput id:<txnid> amount:<amount> address:<recipient>
txnoutput id:<txnid> amount:<change_amount> address:<change_address>
- Inputs MUST equal outputs (or difference is burned as tx fee)
- Include change output to return leftover to yourself
- Either
0x...orMx...address format accepted
4. Check Transaction (optional)
txncheck id:<txnid>
# Response: coins[].input, coins[].output, coins[].difference, burn, valid.*
burnshows how much Minima will be destroyed (input - output difference)valid.basicbecomes true only after adding signatures + MMR proofs
5. List Transaction State (optional)
txnlist id:<txnid>
# Shows full transaction with witness (signatures, mmrproofs, scripts)
6. Sign Transaction
txnsign id:<txnid> publickey:auto
7. Add Coin Proofs and Scripts
txnbasics id:<txnid>
# Populates witness.mmrproofs and witness.scripts
8. Post Transaction
txnpost id:<txnid>
# WARNING: auto:true FAILS for script-address coins. Only use for simple wallet sends.
# txnpost id:<txnid> auto:true
# Add an intentional burn (fee to miners) at post time
txnpost id:<txnid> burn:<amount>
Look Up Address Script
scripts address:<address>
# Returns the KISS VM script controlling the address,
# plus the associated publickey
# Default addresses use: RETURN SIGNEDBY(<publickey>)
Utility Commands
List Coins (UTXOs)
coins relevant:true
Returns all spendable UTXOs with coinid, amount, address, tokenid
Get Block Number
block
Get Node Status
status
Returns: chain (block, time, speed, difficulty, weight, hash)
Enable RPC (Mobile)
rpc enable:true
Enables RPC interface on mobile devices
Coins Relevant — Full Output Format
coins relevant:true
Each coin object:
{
"coinid": "0x...",
"amount": "91.815999",
"address": "0x...",
"tokenid": "0x00",
"token": null,
"floating": false,
"storestate": false,
"state": [],
"mmrentry": "728",
"spent": false,
"created": "203728"
}
token is null for Minima (0x00), or an object with metadata for custom tokens.
Use coinid + address + amount when building manual transactions.
Splitter Pattern (Split One Coin into Many Small Units)
Used when you need many small UTXOs (e.g. for a game that spends 0.1 or 0.01 per action).
A single transaction with one input and N outputs — all in one txnpost:
# 1. Pick a coin with enough Minima (coins relevant:true)
# 2. Create txn, add that coin as input
txncreate id:splitter
txninput id:splitter coinid:<coinid>
# 3. Add N outputs of 0.1 each to recipient
txnoutput id:splitter amount:0.1 address:<recipient>
txnoutput id:splitter amount:0.1 address:<recipient>
# ... repeat for desired number of units
# 4. Add change output back to origin address (coinAmount - N*0.1)
txnoutput id:splitter amount:<change> address:<origin_address>
# 5. Sign, add proofs, and post
txnsign id:splitter publickey:auto
txnbasics id:splitter
txnpost id:splitter
After posting, coins relevant:true will show many coins with amount "0.1".
Tutorial Series Summary
| # | Topic | Key Commands |
|---|---|---|
| 1 | UTXO model — send Minima | `newaddress`, `send`, `balance` |
| 2 | Send tokens | `balance` (find tokenid), `send … tokenid:` |
| 3 | Create token pt.1 | `tokencreate`, read tx response |
| 4 | Create token pt.2 — JSON metadata, splitter | `tokencreate name:{…}`, `coins relevant:true`, `rpc enable:true` |
| 5 | Simple DAO (under construction) | tokens + scripts + voting |
Common Issues
- "Insufficient funds": UTXO locked — wait for previous tx to confirm before spending again
- Second send fails immediately: Both sends spend the same UTXO; only one can win
- Token creation fails: Need enough Minima (even a small fraction) as input
- Balance shows 0 confirmed: Check
unconfirmed— funds pending block confirmation - Wrong amount burned: Always add change output; any input − output difference is permanently burned
- MMR proof error on txncheck: Run
txnbasics(ortxninput … scriptmmr:true) before posting
Resources
- Minima Documentation
- Build Portal
- Discord #app-chat — ask for test Minima coins
tokens
tokens
List all tokens in the unpruned chain.
Optionally import or export tokens to share token data.
tokenid: (optional)
The tokenid of the token to search for or export.
action: (optional)
import : List your existing public keys.
export : Create a new key.
data: (optional)
The data of the token to import, generated from the export.
Examples:
tokens
tokens tokenid:0xFED5..
tokens action:export tokenid:0xFED5..
tokens action:import data:0x000..
sendpoll
sendpoll
Send function that adds 'send' commands to a list and polls every 30 seconds until the return status is 'true'.
Accepts the same parameters as the 'send' function.
action: (optional)
list : list all the 'send' commands in the polling list.
remove : remove a 'send' command from the polling list.
uid: (optional)
The uid of a 'send' command you wish to remove from the polling list. Use with 'action:remove'.
Examples:
sendpoll address:0xFF.. amount:10
sendpoll address:0xFF.. amount:10 tokenid:0xFED5.. burn:0.1
sendpoll action:list
sendpoll action:remove uid:0x..
sendview
sendview
View a transaction ( signed or unsigned ).
View the details of a txn created by the 'sendnosign' command by specifying its .txn file.
file:
Name of the transaction (.txn) file to view, located in the node's base folder.
If not in the base folder, specify the full file path.
Examples:
sendview file:unsignedtransaction-1674907380057.txn
sendview file:C:\Users\signedtransaction-1674907380057.txn
sendpost
sendpost
Post a transaction previously created and signed using the 'sendnosign' and 'sendsign' commands.
Must be posted from an online node within approximately 24 hours of creating to ensure MMR proofs are valid.
file:
Name of the signed transaction (.txn) file to post, located in the node's base folder.
If not in the base folder, specify the full file path.
Examples:
sendpost file:signedtransaction-1674907380057.txn
sendpost file:C:\Users\signedtransaction-1674907380057.txn
sendfrom
[fromaddress:] [address:] [amount:] [script:] [privatekey:] [keyuses:] (tokenid:) (state:) (split:) (burn:) (mine:) - Send Minima or Tokens from a certain address
createfrom
[fromaddress:] [address:] [amount:] (tokenid:) [script:] (burn:) - Create unsigned txn from a certain address
postfrom
| false) (mmr:true |
|---|
tokencreate
tokencreate
Create (mint) custom tokens or NFTs.
You must have some sendable Minima in your wallet as tokens are 'colored coins', a fraction of 1 Minima.
name:
The name of the token. Can be a string or JSON Object.
amount:
The amount of total supply to create for the token. Between 1 and 1 Trillion.
decimals: (optional)
The number of decimal places for the token. Default is 8, maximum 16.
To create NFTs, use 0.
script: (optional)
Add a custom script that must return 'TRUE' when spending any coin of this token.
Both the token script and coin script must return 'TRUE' for a coin to be sendable.
state: (optional)
List of state variables, if adding a script. A JSON object in the format {"port":"value",..}
signtoken: (optional)
Provide a public key to sign the token with.
Useful for proving you are the creator of the token/NFT.
webvalidate: (optional)
Provide a URL to a publicly viewable .txt file you are hosting which stores the tokenid for validation purposes.
Create the file in advance and get the tokenid after the token has been minted.
burn: (optional)
Amount to burn with the tokencreate minting transaction.
mine: (optional)
Mine the TxPoW synchronously.
Examples:
tokencreate name:newtoken amount:1000000
tokencreate amount:10 name:{"name":"newcoin","link":"http:mysite.com","description":"A very cool token"}
tokencreate name:mynft amount:10 decimals:0 webvalidate:https://www.mysite.com/nftvalidation.txt signtoken:0xFF.. burn:0.1
tokencreate name:charitycoin amount:1000 script:"ASSERT VERIFYOUT(@TOTOUT-1 0xMyAddress 1 0x00 TRUE)"
consolidate
consolidate
Consolidate multiple coins (UTxOs) into one by sending them back to yourself. Must have at least 3 coins.
Useful to prevent having many coins of tiny value and to manage the number of coins you are tracking.
Optionally set the minimum coin age (in blocks), maximum number of coins and maximum number of signatures for the transaction.
tokenid:
The tokenid for Minima or custom token to consolidate coins for. Minima is 0x00.
coinage: (optional)
The minimum number of blocks deep (confirmations) a coin needs to be. Default is 3.
maxcoins: (optional)
The maximum number of coins to consolidate. Minimum 3, up to 20.
Coins are first sorted by value (smallest first) before adding to the transaction.
maxsigs: (optional)
The maximum number of signatures for the transaction, up to 5.
Coins are then sorted by address to minimize the number of signatures required.
burn: (optional)
Amount of Minima to burn with the transaction.
debug: (optional)
true or false, true will print more detailed logs.
dryrun: (optional)
true or false, true will simulate the consolidate transaction but not execute it.
password: (optional)
If your Wallet is password locked you can unlock it for this one transaction - then relock it.
Examples:
consolidate tokenid:0x00
consolidate tokenid:0x77.. coinage:10
consolidate tokenid:0x00 maxcoins:5 password:your_password
consolidate tokenid:0x00 coinage:10 maxcoins:8 burn:1
consolidate tokenid:0x00 coinage:10 maxcoins:8 maxsigs:3 burn:1 dryrun:true
coinimport
coinimport
Import a coin including its MMR proof.
Optionally you can track the coin to add it to your relevant coins list and know when it becomes spent.
Importing does not allow the spending of a coin - just the knowledge of its existence.
data:
The data of a coin. Can be found using the 'coinexport' command.
track: (optional)
true or false, true will create an MMR entry for the coin and add it to your relevant coins.
Examples:
coinimport data:0x00000..
cointrack
cointrack
Track or untrack a coin.
Track a coin to keep its MMR proof up-to-date and know when it becomes spent. Stop tracking to remove it from your relevant coins list.
enable:
true or false, true will add the coin to your relevant coins, false will remove it from your relevant coins.
coinid:
The id of a coin. Can be found using the 'coins' command.
Examples:
cointrack enable:true coinid:0xCD34..
txnlist
txnlist
List your custom transactions. Includes previously posted transactions.
Returns the full details of transactions.
id: (optional)
The id of a single transaction to list.
Examples:
txnlist
txnlist id:multisig
txnauto
[id:] [amount:] [address:] (tokenid:) (sign:) (burn:) (mmrscript:) - Create a transaction automatically
txnbasics
txnbasics
Automatically set the MMR proofs and scripts for a transaction.
Only run this when a transaction is ready to be posted.
id:
The id of the transaction.
Examples:
txnbasics id:simpletxn
txncheck
txncheck
Show details about the transaction.
Verify whether the inputs, outputs, signatures, proofs and scripts are valid.
id: (optional)
The id of the transaction to check.
Examples:
txncheck id:multisig
txnoutput
txnoutput
Create a transaction output.
This will create a new coin (UTxO).
If the sum of inputs > outputs, the difference will be burned unless change to the sender is defined as an output.
Optionally store the transaction state variables in the new output coin.
id:
The id of the transaction to add an output to.
amount:
The amount for the output. To send to the specified address.
address:
Address of the recipient/script to send the output to. Can be 0x or Mx address.
tokenid: (optional)
tokenid of the output. Default is Minima (0x00).
storestate: (optional)
true or false, true will keep the state variables of the transaction in the newly created output coin.
Default is true.
Examples:
txnoutput id:simpletxn amount:10 address:0xFED5..
txnoutput id:multisig amount:10 address:0xFED5.. tokenid:0xCEF5.. storestate:false
txnoutput id:eltootxn amount:10 address:0xFED5..
txnscript
txnscript
Add scripts to a transaction.
id:
The id of the transaction.
auto:
Automatically add scripts you know. Useful for multi user transactions.
scripts:
JSON holds the script and the proof in the format {script:proof}
If it is a single script, and not one created with mmrcreate, leave the proof blank.
If it is an mmrcreate script, include the proof.
Examples:
txnscript id:txnmast scripts:{"RETURN TRUE":""}
txnscript id:txnmast scripts:{"RETURN TRUE":"0x000.."}
txnsign
txnsign
Sign a transaction.
Specify the public key or use 'auto' if the coin inputs are simple.
id:
The id of the transaction to sign.
publickey:
The public key specified in a custom script, or 'auto' for transactions with simple inputs.
txnpostauto: (optional)
Do you want to post this transaction. Use the same values as you would for txnpost auto(sort MMR and Scripts)
txnpostburn: (optional)
If you also post this transaction, do you want to add a burn transaction.
txnpostmine: (optional)
If you also post this transaction, do you want to mine it immediately.
txndelete: (optional)
true or false - delete this txn after signing AND posting.
Examples:
txnsign id:simpletxn publickey:auto
txnsign id:simpletxn publickey:auto password:your_password
txnsign id:multisig publickey:0xFD8B..
txnsign id:simpletxn publickey:auto txnpostauto:true
txnpost
txnpost
Post a transaction. Automatically set the Scripts and MMR proofs.
Optionally set a burn for the transaction.
id:
The id of the transaction.
auto: (optional)
Set the scripts and MMR proofs for the transaction.
burn: (optional)
Amount in Minima to burn with the transaction.
mine: (optional)
true or false - should you mine the transaction immediately.
txndelete: (optional)
true or false - delete this txn after posting.
Examples:
txnpost id:simpletxn
txnpost id:simpletxn auto:true burn:0.1
txnpost id:multisig burn:0.1
txnexport
txnexport
Export a transaction as HEX or to a file.
The output can then be imported using 'txnimport' to another node. E.g. For signing.
id:
The id of the transaction to export.
file: (optional)
File name/path to export the transaction to, must use the .txn extension.
Examples:
txnexport id:simpletxn
txnexport id:multisig file:multisig.txn
txnmine
(id:) (data:) - Mine a txn but don't post it from either ID or txnexport Data
Part 4: Hard-Won Lessons from Production MiniDapps
Real patterns, pitfalls, and architectures learned from building production MiniDapps on Minima. If you're building something serious, read this section carefully — it will save you weeks.
MDS.cmd Chain Validation — Never Trust the Response Blindly
When chaining multiple commands with semicolons, each step can fail independently. The response is an array — if step 0 fails, steps 1-4 may be undefined.
WRONG — will crash on corrupt data:
var create = "txnimport id:"+txid+" data:"+txndata+";"
+"txnsign id:"+txid+" publickey:"+pubkey+";"
+"txnexport id:"+txid+";"
+"txndelete id:"+txid+";";
MDS.cmd(create, function(resp){
callback(resp[2].response.data); // CRASH if txnimport failed
});
RIGHT — validate each step:
MDS.cmd(create, function(resp){
if(!resp || !resp[0] || !resp[0].status){
MDS.log("ERROR: txnimport failed: " + JSON.stringify(resp[0].error));
MDS.cmd("txndelete id:"+txid); // cleanup
callback(null);
return;
}
if(!resp[2] || !resp[2].response){
MDS.log("ERROR: txnexport failed");
MDS.cmd("txndelete id:"+txid);
callback(null);
return;
}
callback(resp[2].response.data);
});
Rule: Every caller of a transaction function must check for null returns. Every error path must call txndelete to prevent zombie transactions from locking coins.
Async Callback Discipline
Every MDS function is async. MDS.cmd(), MDS.sql(), computeOutcome(), signTxn() — all use callbacks. Never treat them as synchronous.
WRONG — returns undefined, crashes silently:
var outcome = computeOutcome(secret1, secret2, range);
var result = outcome.result; // outcome is undefined!
RIGHT — use the callback:
computeOutcome(secret1, secret2, range, function(outcome){
var result = outcome.result;
// continue here, not outside
});
This applies to all transaction construction. The entire channel lifecycle is a chain of nested callbacks — if one breaks, the notification is never sent and the channel silently stalls.
txnimport EOFException — Corrupt Transaction Data
txnimport can fail with java.io.EOFException when the transaction data is truncated or corrupted — typically from large Maxima messages between remote nodes. The Java VM throws a NullPointerException when trying to deserialize the broken data.
Symptoms: Channel creation stalls silently. One side sees "Setting up..." forever. No error in the browser console (it happens in service.js on the node).
Fix: Always wrap txnimport in validation:
MDS.cmd("txnimport id:"+txid+" data:"+txndata, function(resp){
if(!resp.status){
MDS.log("txnimport failed: " + resp.error);
callback(null);
return;
}
// safe to continue
});
Service.js + Index.html Architecture — One Brain, One Screen
For any MiniDapp with background processing (games, channels, messaging), use this architecture:
service.js = THE BRAIN — processes all messages, updates DB, signs txns
index.html = THE SCREEN — only displays notifications, handles user clicks
Rules:
service.jsNEVER touches the DOMindex.htmlNEVER processes Maxima messages (except SYNACK for ACK handshake)- Communication is ONE-WAY: service.js → index.html via
MDS.comms.solo() - ONE notification per event, ONE display update per notification
- Stats, balances, logs updated in ONE place only — prevents double-counting
Notification pattern:
// service.js — send notification
function notify(data){
MDS.comms.solo(JSON.stringify(data));
}
notify({type: "GAME_RESULT", result: 3, winner: "player", betamt: "10"});
// index.html — receive notification
MDS.init(function(msg){
if(msg.event === 'MDSCOMMS'){
if(!msg.data.public){
var comms = JSON.parse(msg.data.message);
if(comms.type === 'GAME_RESULT'){
onGameResult(comms); // THE ONLY place results are handled
}
}
}
});
ELTOO Payment Channel State Ports
For game-aware ELTOO channels, use this state port layout:
Port 100: settlement flag (TRUE/FALSE)
Port 101: sequence number (increments with each state update)
Port 102: game phase (0=idle, 1=bet active)
Port 103: bet amount
Port 104: range (2=flip, 6=dice, 36=roulette)
Port 105: player commit hash (SHA3 of player's secret)
Port 106: house commit hash (SHA3 of house's secret)
Port 107: player's pick (0-indexed)
Port 108: bettor indicator (1=user1 is player, 2=user2 is player)
Port 109: user1 payout address (from PREVSTATE, never getaddress)
Port 110: user2 payout address (from PREVSTATE, never getaddress)
Port 111: user1 pre-bet amount
Port 112: user2 pre-bet amount
Port 115: user1 public key (for MAST SIGNEDBY)
Port 116: user2 public key (for MAST SIGNEDBY)
Port 200: channel hashid (for payout tracking)
Critical: All addresses come from PREVSTATE (coin state), never from getaddress at runtime. The minArmyKnife incident lost 5,000 Minima because getaddress returned addresses that later became relevant: false.
Commit-Reveal Randomness
Provably fair random number generation for 2-party games:
// 1. Both sides generate secrets independently
MDS.cmd("random", function(resp){
var secret = resp.response.random; // 64 hex chars
MDS.cmd("hash data:" + secret, function(hashresp){
var commit = hashresp.response.hash;
// Send commit to counterparty, keep secret
});
});
// 2. After both commits are locked in the channel state:
// Both reveal their secrets
// 3. Compute outcome (matches KISS VM on-chain):
var combined = houseSecret + playerSecret.substring(2); // CONCAT, strip 0x from second
MDS.cmd("hash data:" + combined, function(hashresp){
var hash = hashresp.response.hash;
var first4bytes = hash.substring(2, 10); // SUBSET(0, 4, hash)
var num = parseInt(first4bytes, 16); // NUMBER()
var result = num % range; // % range
var winner = (result === playerPick) ? "player" : "house";
});
Why it's fair: Neither side knows the other's secret when committing. The combined hash is deterministic — both compute the same result. On-chain MAST dispute can independently verify using the same math.
Why pessimistic commit: The player's bet is DEDUCTED before the house reveals. If the player loses and walks away, the deducted balance is already the correct state. No "free option" attack.
MAST Dispute Resolution — The 3-Branch Safety Net
Every ELTOO channel should have MAST branches for dispute resolution:
Block 32: DISPUTE — Player proves they won (before house can claim)
Block 256: CLAIM — House claims bet (player walked away after losing)
Block 1024: RECLAIM — Player recovers (house disappeared without revealing)
Every branch must have:
1. SIGNEDBY(key from PREVSTATE) — authorization from coin state, not runtime
2. VERIFYOUT(@INPUT address amount @TOKENID FALSE) — enforces exact payout amounts
3. No unguarded SIGNEDBY path — the minArmyKnife pattern is banned
Script size limit: Main ELTOO script + MAST root must be under 1150 characters. Check with wc -c before deploying. A 1222-char contract once silently failed, losing 343 Minima.
Maxima Messaging Patterns
Message delivery is NOT guaranteed. Always handle:
- Delivery failure:
maxresp.response.delivered === false - Message never arriving: use timeouts (45s+ for game rounds)
- Duplicate messages: use gamephase guards to prevent double-processing
- Out-of-order messages: check state before processing
Large messages can corrupt. Transaction data sent via Maxima between remote nodes can arrive truncated (causes txnimport EOFException). No fix except defensive validation.
ACK handshake: For user-initiated actions (channel requests), use SYNACK/ACK pattern to confirm the counterparty received the message before proceeding.
Common Pitfalls
1. txnpost auto:true is unreliable on script-address coins. Always: txnsign → txnbasics → txnpost separately.
2. @BLKNUM is broken on synced nodes. Use @COINAGE exclusively.
3. Unset STATE ports crash Java VM. Always set unused ports to 0.
4. SQL column names are UPPERCASE in results regardless of CREATE TABLE casing. Use row.BLOCKNUMBER not row.blockNumber.
5. getaddress returns different addresses over time. For contracts, store the address in coin state at creation.
6. MDS session IDs go stale after MiniDapp updates. Users must reopen from MiniHub for a fresh session. The serviceWorker.js Frame removed errors in the browser console are Chrome extension noise, not your bug.
7. Coins are UTXO-locked until the previous transaction confirms. You cannot spend change from an unconfirmed transaction.
8. MDS.sql UPDATE can silently fail (database locked, etc.) and the callback still fires. Always verify critical updates by SELECT after UPDATE.
9. Secrets must be stored by commit hash, not coinid. Coinids change on phase transitions. Commit hashes are permanent.
10. Both players clicking simultaneously causes crossed messages. Use a game request lock — reject incoming GAME_REQUEST if you've already sent one.