@l8b/evm
v1.0.7
Published
**LootiScript API Binding** - EVM blockchain operations using viem for smart contract interactions.
Readme
@l8b/evm
LootiScript API Binding - EVM blockchain operations using viem for smart contract interactions.
Note: This package is used as an API binding for LootiScript in the l8b engine. It provides high-level functions for reading from and writing to smart contracts on EVM-compatible chains (e.g., Base, Ethereum).
API Reference
Contract Operations
evm.read()
Read data from a smart contract (view/pure functions, no transaction).
local result = await evm.read(
"0x...", // Contract address
abi, // Contract ABI (array)
"functionName", // Function name to call
{arg1, arg2} // Function arguments (optional array)
)
// Returns: any (function return value)
// Throws error if read failsExample:
// Read token balance
local balance = await evm.read(
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC contract on Base
[
{
inputs: [{name: "account", type: "address"}],
name: "balanceOf",
outputs: [{name: "", type: "uint256"}],
stateMutability: "view",
type: "function"
}
],
"balanceOf",
{"0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"}
)
// Returns: bigint (balance in smallest unit)evm.write()
Write to a smart contract (state-changing function, sends transaction).
local txHash = await evm.write(
"0x...", // Contract address
abi, // Contract ABI (array)
"functionName", // Function name to call
{arg1, arg2} // Function arguments (optional array)
)
// Returns: string (transaction hash)
// Throws error if transaction fails or user rejectsExample:
// Transfer tokens
local txHash = await evm.write(
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC contract
[
{
inputs: [
{name: "to", type: "address"},
{name: "amount", type: "uint256"}
],
name: "transfer",
outputs: [{name: "", type: "bool"}],
stateMutability: "nonpayable",
type: "function"
}
],
"transfer",
{
"0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", // recipient
"1000000" // amount (6 decimals for USDC)
}
)
// Returns: "0x..." (transaction hash)evm.call()
Simulate a contract call (estimate gas, check if it would succeed, no transaction).
local result = await evm.call(
"0x...", // Contract address
abi, // Contract ABI (array)
"functionName", // Function name to call
{arg1, arg2} // Function arguments (optional array)
)
// Returns: any (simulation result with gas estimate, return value, etc.)
// Throws error if call would failExample:
// Simulate a transfer to check if it would succeed
local simulation = await evm.call(
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
abi,
"transfer",
{recipient, amount}
)
// Returns: { result: bool, request: {...}, gas: bigint, ... }Utility Functions
evm.getBalance()
Get the ETH balance of an address.
local balance = await evm.getBalance("0x...")
// Returns: string (balance in wei as string)
// If address not provided, uses connected wallet addressExample:
// Get balance of connected wallet
local balance = await evm.getBalance()
// Get balance of specific address
local balance = await evm.getBalance("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb")
// Convert to ETH
local ethBalance = evm.formatEther(balance)
print("Balance: " .. ethBalance .. " ETH")evm.formatEther()
Convert wei (as string) to ETH (as string).
local eth = evm.formatEther("1000000000000000000")
// Returns: "1.0" (1 ETH)Example:
local balanceWei = await evm.getBalance()
local balanceEth = evm.formatEther(balanceWei)
print("Balance: " .. balanceEth .. " ETH")evm.parseEther()
Convert ETH (as string) to wei (as string).
local wei = evm.parseEther("1.5")
// Returns: "1500000000000000000" (wei as string)Example:
local amountEth = "0.1"
local amountWei = evm.parseEther(amountEth)
// Use amountWei for transactionsExample Usage
Reading Contract Data
// ERC20 Token ABI (simplified)
local erc20Abi = {
{
inputs: [{name: "account", type: "address"}],
name: "balanceOf",
outputs: [{name: "", type: "uint256"}],
stateMutability: "view",
type: "function"
},
{
inputs: [],
name: "decimals",
outputs: [{name: "", type: "uint8"}],
stateMutability: "view",
type: "function"
},
{
inputs: [],
name: "symbol",
outputs: [{name: "", type: "string"}],
stateMutability: "view",
type: "function"
}
}
async function getTokenInfo()
local tokenAddress = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" // USDC on Base
local userAddress = await wallet.getAddress()
if userAddress == null then
print("No wallet connected")
return
end
try
// Get token balance
local balance = await evm.read(tokenAddress, erc20Abi, "balanceOf", {userAddress})
// Get token decimals
local decimals = await evm.read(tokenAddress, erc20Abi, "decimals", {})
// Get token symbol
local symbol = await evm.read(tokenAddress, erc20Abi, "symbol", {})
// Format balance (divide by 10^decimals)
local formattedBalance = balance / (10 ^ decimals)
print(symbol .. " Balance: " .. formattedBalance)
catch (error)
print("Error reading token info: " .. error)
end
endWriting to Contracts
async function transferTokens()
// Ensure wallet is connected
if wallet.isConnected() == 0 then
await wallet.connect()
end
local tokenAddress = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" // USDC
local recipient = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"
local amount = "1000000" // 1 USDC (6 decimals)
local transferAbi = {
{
inputs: [
{name: "to", type: "address"},
{name: "amount", type: "uint256"}
],
name: "transfer",
outputs: [{name: "", type: "bool"}],
stateMutability: "nonpayable",
type: "function"
}
}
try
// First, simulate to check if it would succeed
local simulation = await evm.call(
tokenAddress,
transferAbi,
"transfer",
{recipient, amount}
)
print("Gas estimate: " .. simulation.gas)
// If simulation succeeds, send the transaction
local txHash = await evm.write(
tokenAddress,
transferAbi,
"transfer",
{recipient, amount}
)
print("Transaction sent: " .. txHash)
// Wait for confirmation...
catch (error)
print("Transfer failed: " .. error)
end
endChecking ETH Balance
async function checkBalance()
local address = await wallet.getAddress()
if address == null then
print("No wallet connected")
return
end
try
local balanceWei = await evm.getBalance(address)
local balanceEth = evm.formatEther(balanceWei)
print("Balance: " .. balanceEth .. " ETH")
print("Balance (wei): " .. balanceWei)
catch (error)
print("Error getting balance: " .. error)
end
endSending ETH
async function sendETH()
if wallet.isConnected() == 0 then
await wallet.connect()
end
local recipient = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"
local amountEth = "0.01"
local amountWei = evm.parseEther(amountEth)
try
// Convert wei string to hex for wallet.sendTransaction
// Note: wallet.sendTransaction expects hex strings
local amountHex = "0x" .. string.format("%x", tonumber(amountWei))
local txHash = await wallet.sendTransaction({
to: recipient,
value: amountHex
})
print("ETH sent: " .. txHash)
catch (error)
print("Send failed: " .. error)
end
endComplex Contract Interaction
async function interactWithNFT()
local nftAddress = "0x..."
// ERC721 ABI (simplified)
local erc721Abi = {
{
inputs: [{name: "tokenId", type: "uint256"}],
name: "ownerOf",
outputs: [{name: "", type: "address"}],
stateMutability: "view",
type: "function"
},
{
inputs: [
{name: "to", type: "address"},
{name: "tokenId", type: "uint256"}
],
name: "transferFrom",
outputs: [],
stateMutability: "nonpayable",
type: "function"
}
}
try
// Check NFT owner
local tokenId = "1"
local owner = await evm.read(nftAddress, erc721Abi, "ownerOf", {tokenId})
print("Owner of token " .. tokenId .. ": " .. owner)
// Transfer NFT (if you own it)
local myAddress = await wallet.getAddress()
if owner == myAddress then
local recipient = "0x..."
local txHash = await evm.write(
nftAddress,
erc721Abi,
"transferFrom",
{myAddress, recipient, tokenId}
)
print("NFT transferred: " .. txHash)
end
catch (error)
print("NFT operation failed: " .. error)
end
endABI Format
The ABI (Application Binary Interface) is an array of function/event definitions. Each function definition includes:
{
inputs: [
{name: "param1", type: "uint256"},
{name: "param2", type: "address"}
],
name: "functionName",
outputs: [{name: "", type: "bool"}],
stateMutability: "view" | "pure" | "nonpayable" | "payable",
type: "function"
}Common Types:
address- Ethereum addressuint256- Unsigned integer (256 bits)uint8- Unsigned integer (8 bits)bool- Booleanstring- Stringbytes- Byte arraybytes32- Fixed-size byte array
Error Handling
All async methods can throw errors:
async function safeContractCall()
try
local result = await evm.read(contractAddress, abi, "functionName", args)
// Use result
catch (error)
// Common errors:
// - "EVM read not available" - Not in Mini App or provider not available
// - "Read contract failed" - Contract call failed
// - "EVM write not available" - Wallet not connected
// - "No account connected" - No wallet account available
// - "Write contract failed" - Transaction failed or rejected
print("Error: " .. error)
end
endBatch Operations
evm.multicall()
Batch multiple contract reads in a single call for better performance.
local requests = {
{address: token1, abi: erc20Abi, functionName: "balanceOf", args: {user}},
{address: token2, abi: erc20Abi, functionName: "balanceOf", args: {user}},
{address: nft, abi: erc721Abi, functionName: "balanceOf", args: {user}},
}
local results = await evm.multicall(requests)
// Returns: Array of results [balance1, balance2, balance3]Example:
async function loadGameState()
local user = await wallet.getAddress()
// Load multiple balances in one call
local balances = await evm.multicall({
{address: "0x...", abi: erc20Abi, functionName: "balanceOf", args: {user}},
{address: "0x...", abi: erc20Abi, functionName: "balanceOf", args: {user}},
})
print("Token 1: " .. balances[1])
print("Token 2: " .. balances[2])
endEvent Operations
evm.watchEvent()
Watch contract events in real-time. Returns an unsubscribe function.
local unsubscribe = await evm.watchEvent(
"0x...", // Contract address
abi, // Contract ABI
"Transfer", // Event name
{ // Filter options (optional)
fromBlock: "latest",
args: {from: "0x..."}
},
function(event) // Callback function
print("Event: " .. event.tokenId)
end
)
// Stop watching
unsubscribe()Example:
async function watchNFTTransfers()
local nftAddress = "0x..."
local erc721Abi = {...}
local unsubscribe = await evm.watchEvent(
nftAddress,
erc721Abi,
"Transfer",
{},
function(event)
print("NFT transferred!")
print("From: " .. event.args.from)
print("To: " .. event.args.to)
print("Token ID: " .. event.args.tokenId)
end
)
// Cleanup when done
// unsubscribe()
endevm.getEventLogs()
Get historical event logs from a contract.
local logs = await evm.getEventLogs(
"0x...", // Contract address
abi, // Contract ABI
"Transfer", // Event name
{ // Filter options (optional)
fromBlock: 1000000,
toBlock: "latest",
args: {from: "0x..."}
}
)
// Returns: Array of event logsExample:
async function getTransferHistory()
local logs = await evm.getEventLogs(
"0x...",
erc721Abi,
"Transfer",
{
fromBlock: 1000000,
toBlock: "latest",
}
)
for i, log in ipairs(logs) do
print("Transfer #" .. i)
print("Token ID: " .. log.args.tokenId)
print("Block: " .. log.blockNumber)
end
endTransaction Utilities
evm.getTransactionReceipt()
Get transaction receipt with status, gas used, logs, etc.
local receipt = await evm.getTransactionReceipt("0x...")
// Returns: {
// status: "success" | "reverted",
// transactionHash: string,
// blockNumber: string,
// gasUsed: string,
// effectiveGasPrice?: string,
// logs: Array,
// contractAddress?: string
// }Example:
async function checkTransaction()
local txHash = await evm.write(...)
// Wait for receipt
local receipt = await evm.getTransactionReceipt(txHash)
if receipt.status == "success" then
print("Transaction confirmed!")
print("Gas used: " .. receipt.gasUsed)
print("Block: " .. receipt.blockNumber)
else
print("Transaction reverted!")
end
endevm.estimateGas()
Estimate gas cost for a contract write operation.
local gasEstimate = await evm.estimateGas(
"0x...", // Contract address
abi, // Contract ABI
"functionName", // Function name
{arg1, arg2} // Function arguments (optional)
)
// Returns: string (gas estimate in wei)Example:
async function checkGasCost()
local gasEstimate = await evm.estimateGas(
"0x...",
abi,
"transfer",
{recipient, amount}
)
print("Estimated gas: " .. gasEstimate)
// Show gas cost to user before sending
// Then proceed with evm.write()
endNotes
- All async methods return Promises and should be awaited
evm.read()andevm.call()don't send transactions (no gas cost, read-only)evm.write()sends a transaction (requires gas, user confirmation)- The default chain is Base (chain ID 8453)
- ABI can be obtained from contract verification on block explorers or contract source code
- For ERC20/ERC721 tokens, use standard ABIs available online
- Large numbers (uint256) are returned as BigInt or string - handle accordingly
- Always simulate transactions with
evm.call()before sending withevm.write() - Use
evm.multicall()to batch multiple reads for better performance - Use
evm.watchEvent()for real-time updates (remember to unsubscribe when done) - Use
evm.estimateGas()to show gas cost to users before sending transactions - Gas estimation is handled automatically by the wallet provider, but explicit estimation is available
