Lua Script Examples

Real-world Lua scripts demonstrating common use cases. These examples are from the Subfrost reference implementation.

multicall.lua

Execute multiple RPC calls in a single batch request.

-- Execute multiple RPC calls in a single batch
-- Args: array of [method, params] tuples
-- Example: [["btc_getblockcount", []], ["btc_getblockhash", [100]]]

local results = {}

for i, call in ipairs(args) do
    if type(call) ~= "table" or #call ~= 2 then
        return {
            error = "Each multicall entry must be a tuple of [method, params]",
            index = i
        }
    end
    
    local method = call[1]
    local params = call[2]
    
    if type(method) ~= "string" then
        return { error = "Method name must be a string", index = i }
    end
    
    if type(params) ~= "table" then
        return { error = "Method params must be an array", index = i }
    end
    
    local success, result = pcall(function()
        local rpc_func = _RPC[method]
        if not rpc_func then
            error("Method not found: " .. method)
        end
        
        if #params == 0 then
            return rpc_func()
        elseif #params == 1 then
            return rpc_func(params[1])
        elseif #params == 2 then
            return rpc_func(params[1], params[2])
        else
            local unpack_func = table.unpack or unpack
            return rpc_func(unpack_func(params))
        end
    end)
    
    if success then
        table.insert(results, { result = result })
    else
        table.insert(results, { error = { message = tostring(result) } })
    end
end

return results

Usage:

{
  "jsonrpc": "2.0",
  "method": "lua_evalscript",
  "params": [
    "-- multicall script here --",
    [["btc_getblockcount", []], ["btc_getblockhash", [850000]], ["esplora_feeestimates", []]]
  ],
  "id": 1
}

balances.lua

Comprehensive balance information for an address, combining UTXOs with ordinal and alkane data.

-- Comprehensive balance information for an address
-- Args: address, protocol_tag (optional, default: "1")

local address = args[1]
local protocol_tag = args[2] or "1"

-- Get ord and metashrew heights
local ord_height = _RPC.ord_blockheight() or 0
local metashrew_height_str = _RPC.metashrew_height() or "0"
local metashrew_height = tonumber(metashrew_height_str) or 0
local max_indexed_height = math.max(ord_height, metashrew_height)

-- Get UTXOs
local utxos = _RPC.esplora_addressutxo(address) or {}

-- Get protorunes/alkanes data
local protorunes = _RPC.alkanes_protorunesbyaddress({
    address = address,
    protocolTag = protocol_tag
}) or {}

-- Get ord outputs (inscriptions and runes)
local ord_outputs = _RPC.ord_outputs(address) or {}

-- Build lookup maps
local runes_map = {}
if protorunes.outpoints then
    for _, outpoint in ipairs(protorunes.outpoints) do
        if outpoint.outpoint and outpoint.runes then
            local key = outpoint.outpoint.txid .. ":" .. outpoint.outpoint.vout
            runes_map[key] = outpoint.runes
        end
    end
end

local ord_outputs_map = {}
for _, output in ipairs(ord_outputs) do
    if output.outpoint then
        ord_outputs_map[output.outpoint] = {
            inscriptions = output.inscriptions or {},
            ord_runes = output.runes or {}
        }
    end
end

-- Categorize UTXOs
local spendable = {}
local assets = {}
local pending = {}

for _, utxo in ipairs(utxos) do
    local key = utxo.txid .. ":" .. utxo.vout
    local height = utxo.status and utxo.status.block_height
    
    local utxo_entry = {
        outpoint = key,
        value = utxo.value
    }
    
    if height then utxo_entry.height = height end
    if runes_map[key] then utxo_entry.runes = runes_map[key] end
    
    if ord_outputs_map[key] then
        if #ord_outputs_map[key].inscriptions > 0 then
            utxo_entry.inscriptions = ord_outputs_map[key].inscriptions
        end
        if next(ord_outputs_map[key].ord_runes) ~= nil then
            utxo_entry.ord_runes = ord_outputs_map[key].ord_runes
        end
    end
    
    local has_assets = (utxo_entry.runes and #utxo_entry.runes > 0) or
                      (utxo_entry.inscriptions and #utxo_entry.inscriptions > 0) or
                      (utxo_entry.ord_runes and next(utxo_entry.ord_runes) ~= nil)
    
    local is_confirmed = height and height <= max_indexed_height
    
    if not is_confirmed then
        table.insert(pending, utxo_entry)
    elseif has_assets then
        table.insert(assets, utxo_entry)
    else
        table.insert(spendable, utxo_entry)
    end
end

return {
    spendable = spendable,
    assets = assets,
    pending = pending,
    ordHeight = ord_height,
    metashrewHeight = metashrew_height
}

address_utxos_with_txs.lua

Batch fetch UTXOs with full transaction details.

-- Batch fetch UTXOs for an address with full transaction details
-- Args: address

local address = args[1]

local utxos = _RPC.esplora_addressutxo(address)
if not utxos then
    return { utxos = {}, error = "Failed to fetch UTXOs" }
end

local result = { utxos = {}, count = 0 }

for i, utxo in ipairs(utxos) do
    local tx_data = _RPC.esplora_tx(utxo.txid)
    
    local utxo_entry = {
        txid = utxo.txid,
        vout = utxo.vout,
        value = utxo.value,
        status = utxo.status,
        tx = tx_data
    }
    
    table.insert(result.utxos, utxo_entry)
    result.count = result.count + 1
end

return result

batch_utxo_balances.lua

Fetch UTXOs with alkane balances for each.

-- Batch UTXO balance fetching for alkanes
-- Args: address, protocol_tag (default: 1), block_tag (optional)

local address = args[1]
local protocol_tag = args[2] or 1
local block_tag = args[3]

local utxos = _RPC.esplora_addressutxo(address)
if not utxos then
    return { utxos = {}, error = "Failed to fetch UTXOs" }
end

local result = { utxos = {}, count = 0 }

for i, utxo in ipairs(utxos) do
    local balance_response = _RPC.protorunes_by_outpoint(
        utxo.txid,
        utxo.vout,
        block_tag,
        protocol_tag
    )
    
    local utxo_entry = {
        txid = utxo.txid,
        vout = utxo.vout,
        value = utxo.value,
        status = utxo.status,
        balances = {}
    }
    
    if balance_response and balance_response.balance_sheet then
        local cached = balance_response.balance_sheet.cached
        if cached and cached.balances then
            for alkane_id, amount in pairs(cached.balances) do
                table.insert(utxo_entry.balances, {
                    block = alkane_id.block,
                    tx = alkane_id.tx,
                    amount = amount
                })
            end
        end
    end
    
    table.insert(result.utxos, utxo_entry)
    result.count = result.count + 1
end

return result

Custom Examples

Get Inscription Content

local inscription_id = args[1]

local inscription = _RPC.ord_inscription(inscription_id)
if not inscription then
    return { error = "Inscription not found" }
end

local content = _RPC.ord_content(inscription_id)

return {
    id = inscription.id,
    number = inscription.number,
    content_type = inscription.content_type,
    content_length = inscription.content_length,
    content_base64 = content,
    owner = inscription.address,
    sat = inscription.sat
}

Monitor Rune Minting Progress

local rune_name = args[1]

local rune = _RPC.ord_rune(rune_name)
if not rune or not rune.entry then
    return { error = "Rune not found" }
end

local entry = rune.entry
local mints = tonumber(entry.mints) or 0
local cap = entry.terms and entry.terms.cap or 0

return {
    name = entry.spaced_rune,
    symbol = entry.symbol,
    mints = mints,
    cap = cap,
    progress_percent = cap > 0 and (mints / cap * 100) or 100,
    remaining = cap - mints,
    fully_minted = mints >= cap
}

Check Transaction Confirmation

local txid = args[1]
local required_confirmations = args[2] or 6

local current_height = _RPC.btc_getblockcount()
local tx = _RPC.esplora_tx(txid)

if not tx then
    return { error = "Transaction not found", txid = txid }
end

local confirmations = 0
if tx.status and tx.status.confirmed and tx.status.block_height then
    confirmations = current_height - tx.status.block_height + 1
end

return {
    txid = txid,
    confirmed = tx.status.confirmed,
    block_height = tx.status.block_height,
    confirmations = confirmations,
    meets_requirement = confirmations >= required_confirmations,
    required = required_confirmations
}