Minima Blockchain — Complete Developer ReferenceDownload .md

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

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:

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

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

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

Signature Functions

Output/Input Verification

Cryptography

Global Variables

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

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

Common Mistakes

MistakeResultFix
Underscore in variable nameSilent parse error at runtimeUse camelCase/flat
VERIFYOUT FALSE + storestate:trueTx silently rejectedUse TRUE for phase transitions
@BLKNUM for deadlinesTx rejected on synced nodesUse @COINAGE
XOR for bitwise operationsScript failsSHA3(CONCAT()) instead
NUMBER(SHA3(x))Overflow, script failsSUBSET(0 4 hash) first
Script > 1200 charsSilently rejected on-chainMinify variables, use MAST
SAMESTATE(0 11) when port 6 changesSAMESTATE failsSplit: 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:

Token ID

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:

Send Minima


send address:<recipient> amount:<amount>

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>

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):

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)

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>

4. Check Transaction (optional)


txncheck id:<txnid>
# Response: coins[].input, coins[].output, coins[].difference, burn, valid.*

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

#TopicKey Commands
1UTXO model — send Minima`newaddress`, `send`, `balance`
2Send tokens`balance` (find tokenid), `send … tokenid:`
3Create token pt.1`tokencreate`, read tx response
4Create token pt.2 — JSON metadata, splitter`tokencreate name:{…}`, `coins relevant:true`, `rpc enable:true`
5Simple DAO (under construction)tokens + scripts + voting

Common Issues

Resources

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:

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:

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.