This document describes the C ABI for embedding Zig EVM in other languages.
The FFI layer provides:
- Opaque handles for EVM instances
- C-compatible data structures
- Thread-safe operations
- Error codes for all operations
# Build shared and static libraries
zig build lib
# Output files:
# - zig-out/lib/libzigevm.so (Linux)
# - zig-out/lib/libzigevm.dylib (macOS)
# - zig-out/lib/libzigevm.dll (Windows)
# - zig-out/lib/libzigevm.a (static)
# - zig-out/include/zigevm.h (header)Include the header in your C/C++ project:
#include "zigevm.h"typedef enum {
EVM_OK = 0,
EVM_OUT_OF_GAS = 1,
EVM_STACK_UNDERFLOW = 2,
EVM_STACK_OVERFLOW = 3,
EVM_INVALID_OPCODE = 4,
EVM_INVALID_JUMP = 5,
EVM_REVERT = 6,
EVM_STATIC_CALL_VIOLATION = 7,
EVM_OUT_OF_MEMORY = 8,
EVM_CALL_DEPTH_EXCEEDED = 9,
EVM_INSUFFICIENT_BALANCE = 10,
EVM_INVALID_ARGUMENT = 11,
EVM_UNKNOWN_ERROR = 255,
} EVMError;EVMHandle evm_create(void);Create a new EVM instance.
Returns: Handle to the EVM, or NULL on failure.
Example:
EVMHandle evm = evm_create();
if (evm == NULL) {
fprintf(stderr, "Failed to create EVM\n");
return 1;
}void evm_destroy(EVMHandle handle);Destroy an EVM instance and free all resources.
Parameters:
handle: EVM handle fromevm_create()
void evm_reset(EVMHandle handle);Reset EVM state for new execution. Preserves accounts and storage, clears stack/memory.
void evm_set_gas_limit(EVMHandle handle, uint64_t gas_limit);Set the maximum gas allowed for execution.
void evm_set_block_number(EVMHandle handle, uint64_t number);Set the current block number (for BLOCKNUMBER opcode).
void evm_set_timestamp(EVMHandle handle, uint64_t timestamp);Set the block timestamp (for TIMESTAMP opcode).
void evm_set_chain_id(EVMHandle handle, uint64_t chain_id);Set the chain ID (1 = mainnet, etc.).
void evm_set_coinbase(EVMHandle handle, const uint8_t* addr);Set the coinbase (block producer) address.
Parameters:
addr: 20-byte address
void evm_set_address(EVMHandle handle, const uint8_t* addr);Set the current contract address (for ADDRESS opcode).
void evm_set_caller(EVMHandle handle, const uint8_t* addr);Set the caller address (for CALLER opcode).
void evm_set_origin(EVMHandle handle, const uint8_t* addr);Set the transaction origin (for ORIGIN opcode).
void evm_set_value(EVMHandle handle, const uint8_t* value);Set the call value (for CALLVALUE opcode).
Parameters:
value: 32-byte big-endian value
EVMError evm_set_balance(EVMHandle handle, const uint8_t* addr, const uint8_t* balance);Set an account's balance.
Parameters:
addr: 20-byte addressbalance: 32-byte big-endian balance
Returns: EVM_OK on success.
EVMError evm_set_code(EVMHandle handle, const uint8_t* addr,
const uint8_t* code, size_t code_len);Set an account's bytecode.
Parameters:
addr: 20-byte addresscode: Pointer to bytecodecode_len: Length of bytecode
EVMError evm_set_storage(EVMHandle handle, const uint8_t* addr,
const uint8_t* key, const uint8_t* value);Set a storage slot value.
Parameters:
addr: 20-byte addresskey: 32-byte storage keyvalue: 32-byte storage value
EVMError evm_get_storage(EVMHandle handle, const uint8_t* addr,
const uint8_t* key, uint8_t* out);Get a storage slot value.
Parameters:
addr: 20-byte addresskey: 32-byte storage keyout: 32-byte buffer to receive value
EVMResult evm_execute(EVMHandle handle, const uint8_t* code, size_t code_len,
const uint8_t* calldata, size_t calldata_len);Execute EVM bytecode.
Parameters:
code: Pointer to bytecodecode_len: Length of bytecodecalldata: Pointer to calldata (can be NULL if calldata_len is 0)calldata_len: Length of calldata
Returns: EVMResult structure:
typedef struct {
bool success; // True if execution succeeded
EVMError error_code; // Error code if failed
uint64_t gas_used; // Gas consumed
uint64_t gas_remaining; // Gas remaining
uint8_t* return_data; // Return data (owned by EVM)
size_t return_data_len; // Return data length
bool reverted; // True if REVERT was called
} EVMResult;Example:
// Bytecode: PUSH1 3, PUSH1 5, ADD
uint8_t code[] = {0x60, 0x03, 0x60, 0x05, 0x01};
EVMResult result = evm_execute(evm, code, sizeof(code), NULL, 0);
if (result.success) {
printf("Gas used: %lu\n", result.gas_used);
} else {
printf("Error: %d\n", result.error_code);
}uint64_t evm_gas_used(EVMHandle handle);Get gas used in last execution.
uint64_t evm_gas_remaining(EVMHandle handle);Get remaining gas after last execution.
size_t evm_return_data_len(EVMHandle handle);Get return data length.
size_t evm_return_data_copy(EVMHandle handle, uint8_t* out, size_t max_len);Copy return data to buffer.
Returns: Number of bytes copied.
size_t evm_logs_count(EVMHandle handle);Get number of logs emitted.
bool evm_log_address(EVMHandle handle, size_t index, uint8_t* out);Get log address at index.
Parameters:
index: Log indexout: 20-byte buffer to receive address
Returns: true on success, false if index out of bounds.
size_t evm_log_topics_count(EVMHandle handle, size_t index);Get number of topics (0-4) for a log.
bool evm_log_topic(EVMHandle handle, size_t log_index,
size_t topic_index, uint8_t* out);Get a specific topic from a log.
Parameters:
log_index: Log indextopic_index: Topic index (0-3)out: 32-byte buffer to receive topic
size_t evm_log_data_len(EVMHandle handle, size_t index);Get log data length.
size_t evm_log_data_copy(EVMHandle handle, size_t index,
uint8_t* out, size_t max_len);Copy log data to buffer.
size_t evm_stack_depth(EVMHandle handle);Get number of items on stack.
bool evm_stack_peek(EVMHandle handle, size_t index, uint8_t* out);Peek stack value at index (0 = top).
Parameters:
index: Stack index from topout: 32-byte buffer to receive value
size_t evm_memory_size(EVMHandle handle);Get memory size in bytes.
size_t evm_memory_copy(EVMHandle handle, size_t offset,
uint8_t* out, size_t len);Copy memory region to buffer.
const char* evm_version(void);Get library version string.
For parallel transaction processing.
typedef struct {
uint32_t max_threads; // Maximum worker threads
bool enable_parallel; // Enable parallel execution
bool enable_speculation; // Enable speculative execution
uint64_t chain_id; // Chain ID
uint64_t block_number; // Block number
uint64_t block_timestamp; // Block timestamp
uint64_t block_gas_limit; // Block gas limit
uint8_t coinbase[20]; // Coinbase address
} BatchConfig;typedef struct {
uint8_t from[20]; // Sender address
uint8_t to[20]; // Recipient address
bool has_to; // True if to address is set
uint8_t value[32]; // Value in wei (big-endian)
const uint8_t* data; // Calldata
size_t data_len; // Calldata length
uint64_t gas_limit; // Gas limit
uint8_t gas_price[32]; // Gas price (big-endian)
uint64_t nonce; // Nonce
bool has_nonce; // True if nonce is set
} BatchTransaction;typedef struct {
uint32_t tx_index; // Transaction index
bool success; // True if succeeded
bool reverted; // True if reverted
uint64_t gas_used; // Gas used
uint8_t* return_data; // Return data pointer
size_t return_data_len; // Return data length
EVMError error_code; // Error code
size_t logs_count; // Number of logs
uint8_t created_address[20]; // Created contract address
bool has_created_address; // True if contract created
} BatchResult;typedef struct {
uint32_t total_transactions; // Total transactions
uint32_t successful_transactions; // Successful count
uint32_t failed_transactions; // Failed count
uint32_t reverted_transactions; // Reverted count
uint64_t total_gas_used; // Total gas used
uint64_t execution_time_ns; // Execution time
uint32_t parallel_waves; // Parallel waves
uint32_t max_parallelism; // Max parallelism
} BatchStats;BatchHandle batch_create(const BatchConfig* config);Create a batch executor.
void batch_destroy(BatchHandle handle);Destroy a batch executor.
EVMError batch_set_account(BatchHandle handle, const uint8_t* addr,
const uint8_t* balance, uint64_t nonce,
const uint8_t* code, size_t code_len);Set account state in batch executor.
EVMError batch_set_storage(BatchHandle handle, const uint8_t* addr,
const uint8_t* key, const uint8_t* value);Set storage in batch executor.
EVMError batch_execute(BatchHandle handle,
const BatchTransaction* transactions,
size_t tx_count, BatchStats* stats_out);Execute a batch of transactions.
Example:
BatchConfig config = {
.max_threads = 4,
.enable_parallel = true,
.enable_speculation = false,
.chain_id = 1,
.block_number = 12345678,
.block_timestamp = 1234567890,
.block_gas_limit = 30000000,
.coinbase = {0}
};
BatchHandle batch = batch_create(&config);
// Set up accounts...
BatchTransaction txs[100];
// Fill transactions...
BatchStats stats;
EVMError err = batch_execute(batch, txs, 100, &stats);
printf("Executed %u txs in %lu ns\n",
stats.total_transactions, stats.execution_time_ns);
printf("Parallel waves: %u, Max parallelism: %u\n",
stats.parallel_waves, stats.max_parallelism);
batch_destroy(batch);size_t batch_results_count(BatchHandle handle);Get number of results from last execution.
bool batch_get_result(BatchHandle handle, size_t index, BatchResult* result_out);Get a specific result.
size_t batch_result_return_data(BatchHandle handle, size_t index,
uint8_t* out, size_t max_len);Copy return data from a result.
#include <stdio.h>
#include <string.h>
#include "zigevm.h"
int main() {
// Create EVM
EVMHandle evm = evm_create();
if (!evm) {
fprintf(stderr, "Failed to create EVM\n");
return 1;
}
// Configure
evm_set_gas_limit(evm, 1000000);
evm_set_chain_id(evm, 1);
// Set up account
uint8_t addr[20] = {0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
0xaa, 0xaa, 0xaa, 0xaa, 0xaa};
uint8_t balance[32] = {0};
balance[31] = 100; // 100 wei
evm_set_balance(evm, addr, balance);
evm_set_address(evm, addr);
// Bytecode: PUSH1 42, PUSH1 0, MSTORE, PUSH1 32, PUSH1 0, RETURN
uint8_t code[] = {
0x60, 0x2a, // PUSH1 42
0x60, 0x00, // PUSH1 0
0x52, // MSTORE
0x60, 0x20, // PUSH1 32
0x60, 0x00, // PUSH1 0
0xf3 // RETURN
};
// Execute
EVMResult result = evm_execute(evm, code, sizeof(code), NULL, 0);
if (result.success) {
printf("Execution successful!\n");
printf("Gas used: %lu\n", result.gas_used);
printf("Return data length: %zu\n", result.return_data_len);
if (result.return_data_len > 0) {
printf("Return data: ");
for (size_t i = 0; i < result.return_data_len; i++) {
printf("%02x", result.return_data[i]);
}
printf("\n");
}
} else {
printf("Execution failed with error: %d\n", result.error_code);
if (result.reverted) {
printf("Transaction reverted\n");
}
}
// Cleanup
evm_destroy(evm);
return 0;
}gcc -o myapp myapp.c -L/path/to/lib -lzigevm -Wl,-rpath,/path/to/libcl myapp.c /I path\to\include /link path\to\lib\zigevm.lib- Each EVM instance is NOT thread-safe
- Create separate instances for concurrent use
- BatchExecutor handles internal threading
- FFI functions are safe to call from any thread