About Autometa
π‘ Inspiration
The inspiration for Autometa came from a simple frustration: why is blockchain automation so hard?
In traditional software, we have cron jobs, task schedulers, and automation platforms like Zapier. But in Web3, executing recurring tasks requires either:
- Running your own infrastructure 24/7
- Trusting centralized services with your private keys
- Manually executing transactions every single time
We asked ourselves: "What if you could automate blockchain tasks as easily as setting a calendar reminder?"
That's when Autometa was born.
We chose to build on Polkadot's Moonbeam parachain because it offers the best of both worlds:
- Ethereum compatibility - Use familiar tools and contracts
- Polkadot security - Benefit from shared security across the entire ecosystem
- Low fees - Affordable automation for everyone
- Fast finality - Quick execution with ~12 second block times
π What We Learned
Building Autometa taught us invaluable lessons about blockchain architecture, decentralized systems, and user experience:
1. Smart Contract Design Challenges
We learned that gas optimization mattersβa lot. Our initial ActionExecutor contract consumed excessive gas due to inefficient data structures. We refactored to use:
// Before: Multiple storage reads
function execute(uint256 workflowId) external {
Workflow memory wf = workflows[workflowId]; // SLOAD
require(wf.active, "Inactive"); // Check
// ... more SLOADs
}
// After: Single storage read with memory operations
function execute(uint256 workflowId) external {
Workflow memory wf = workflows[workflowId]; // One SLOAD
// All operations use memory copy
}
Result: ~40% gas savings per execution.
2. EVM Event Indexing at Scale
We discovered that Moonbase Alpha's RPC has strict query limits (1024 blocks max). Fetching historical events required implementing:
// Chunked event fetching with exponential backoff
const chunks = [];
for (let i = 0; i < totalBlocks; i += 1000) {
const events = await fetchWithRetry(() =>
contract.getEvents({
fromBlock: startBlock + i,
toBlock: Math.min(startBlock + i + 999, endBlock)
})
);
chunks.push(...events);
}
Lesson: Always design for RPC limitations in production systems.
3. Race Conditions in Distributed Workers
Our initial worker design had a critical flaw: multiple workers could execute the same workflow simultaneously! We solved this with Redis-based atomic locks:
# Atomic workflow locking
def acquire_lock(workflow_id: int) -> bool:
lock_key = f"workflow:{workflow_id}:lock"
return redis.set(lock_key, worker_id, nx=True, ex=300) # 5min TTL
Takeaway: Distributed systems require careful synchronization.
4. Web3 UX is Still Hard
We learned that Web3 UX requires hand-holding at every step:
- Users don't understand gas deposits vs. execution costs
- Transaction confirmations feel like black boxes
- Error messages from contracts are cryptic
Our solution: Contextual explanations everywhere:
- "Why do I need to deposit gas?" tooltips
- Real-time transaction status updates
- Human-readable error translations
ποΈ How We Built Autometa
Architecture Overview
Autometa is a full-stack decentralized application built on Moonbeam (Polkadot parachain) with the following components:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Frontend (Next.js 15) β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β Dashboard β β Templates β β Wallet (Web3)β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
ββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β HTTP/WebSocket
ββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ
β Backend API (FastAPI + Python) β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β Workflow β β Escrow β β Registry β β
β β Service β β Service β β Service β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
ββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β Web3.py
ββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ
β Moonbase Alpha (Polkadot Parachain) β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β Workflow β β Action β β Fee β β
β β Registry β β Executor β β Escrow β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
ββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ
β Worker Infrastructure (Python + Redis) β
β ββββββββββββββββ ββββββββββββββββ β
β β Scheduler βββRedisβββΆ β Worker β β
β β (Scan chain)β Queue β (Execute) β β
β ββββββββββββββββ ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Technology Stack
Smart Contracts (Solidity 0.8.20)
WorkflowRegistry.sol- Workflow storage and state managementActionExecutor.sol- Execute actions (transfers, contract calls)FeeEscrow.sol- Gas fee management with deposit/withdraw
Backend (Python 3.10+)
- FastAPI for REST API
- Web3.py for blockchain interaction
- Redis for job queue
- Uvicorn ASGI server
Frontend (TypeScript + React)
- Next.js 15 with App Router
- Viem for wallet integration
- Tailwind CSS + shadcn/ui
- RainbowKit for wallet connection
Worker Services
- Scheduler: Scans chain every 10s for due workflows
- Worker: Executes workflows from Redis queue
- Atomic locks prevent duplicate execution
Key Implementation Details
1. Workflow Execution Flow
# Scheduler (runs every 10 seconds)
def scan_workflows():
current_time = int(time.time())
workflows = registry.get_all_workflows()
for wf in workflows:
if wf['active'] and wf['next_run'] <= current_time:
redis.rpush('workflow_queue', wf['id'])
# Worker (processes queue)
def execute_workflow(workflow_id):
# 1. Acquire lock
if not acquire_lock(workflow_id):
return # Another worker got it
# 2. Check escrow balance
balance = escrow.get_balance(owner)
if balance < gas_budget:
log.error(f"Insufficient balance")
return
# 3. Execute on-chain
tx_hash = registry.execute_workflow(workflow_id)
# 4. Release lock
release_lock(workflow_id)
2. Gas-Efficient Action Encoding
We encode action parameters efficiently to minimize storage costs:
// NATIVE transfer: [type(1B)][address(20B)][amount(32B)] = 53 bytes
bytes memory actionData = abi.encodePacked(
uint8(1), // Action type
address(recipient), // 20 bytes
uint256(amount) // 32 bytes
);
// ERC20 transfer: [type(1B)][token(20B)][recipient(20B)][amount(32B)] = 73 bytes
bytes memory actionData = abi.encodePacked(
uint8(2),
address(token),
address(recipient),
uint256(amount)
);
3. User-Signed Workflow Creation
Users sign their own workflow creation transactions (no relayer needed):
// Frontend: User signs transaction
const hash = await writeContract({
address: WORKFLOW_REGISTRY_ADDRESS,
abi: WorkflowRegistryABI,
functionName: 'createWorkflow',
args: [triggerType, triggerData, actionType, actionData, interval, gasBudget],
account: userAddress // User's wallet
});
// Result: User owns the workflow, controls activation
π§ Challenges We Faced
Challenge 1: Gas Overdraft Crisis
Problem: The FeeEscrow contract charged gas before checking execution success. Failed workflows still consumed gas, leading to:
- User deposited 0.2 DEV
- System charged 1.5 DEV (overdraft!)
- Broken workflows in infinite retry loops
Solution:
// Before: Charge first, execute later
function executeWorkflow(uint256 workflowId) external {
_chargeGas(owner, gasBudget); // Charged even if execution fails
_executeAction(actionData);
}
// After: Execute first, charge only on success
function executeWorkflow(uint256 workflowId) external {
_executeAction(actionData); // Revert if fails
_chargeGas(owner, gasBudget); // Only charged on success
}
We also added worker-side balance checks to prevent execution attempts when insufficient funds.
Challenge 2: Field Mapping Bug
Problem: Backend was reading wrong indices from smart contract, causing:
# Contract returns: (owner, triggerType, triggerData, actionType, actionData, nextRun, ...)
# 0 1 2 3 4 5
# Backend read:
workflow['next_run'] = wf[3] # Actually actionType (value: 1)
workflow['action_type'] = wf[5] # Actually nextRun (timestamp)
Result: Workflows showed "Next run: 1/1/1970" (Unix epoch 1)
Fix: Corrected field mapping to match Solidity struct order:
workflow = {
'next_run': wf[5], # Correct index
'action_type': wf[3], # Correct index
# ...
}
Challenge 3: Missing ActionType Byte
Problem: ActionExecutor expected actionData format:
[actionType(1B)][params...]
But frontend was sending:
[params...] # Missing the type byte!
Result: All executions reverted with "Invalid actionType"
Solution: Updated encoder to prepend the type:
// Before
export function encodeNativeTransfer(to: string, amount: bigint): string {
return ethers.solidityPacked(['address', 'uint256'], [to, amount]);
}
// After
export function encodeNativeTransfer(to: string, amount: bigint): string {
return ethers.solidityPacked(
['uint8', 'address', 'uint256'], // β
Prepend type
[1, to, amount] // 1 = NATIVE
);
}
Challenge 4: RPC Rate Limiting
Problem: Moonbase Alpha RPC returns "block range too wide" for queries >1024 blocks.
Solution: Implemented chunked fetching with retry logic:
const fetchWithRetry = async (fetcher: Function, maxRetries = 3) => {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fetcher();
} catch (error) {
if (attempt === maxRetries - 1) throw error;
await sleep(1000 * Math.pow(2, attempt)); // Exponential backoff
}
}
};
Challenge 5: NULL Next Run Times
Problem: Some workflows had nextRun: null, preventing scheduling.
Root Cause: Frontend calculated nextRun = now + interval, but for immediate execution we wanted now + 60s. However, transaction delays caused the timestamp to be in the past by the time it confirmed.
Solution: Set nextRun = Math.floor(Date.now() / 1000) + 60 for immediate execution, giving a safe 60-second buffer.
π― What's Next
Autometa is just getting started. Our roadmap includes:
Phase 1: Enhanced Triggers (Q1 2026)
- Price oracles integration (Chainlink on Moonbeam)
- Wallet balance monitoring
- Event-based triggers (e.g., "when NFT minted")
Phase 2: Multi-Chain Support (Q2 2026)
- Expand to Moonriver (Kusama)
- Cross-chain workflows via XCM
- Polkadot relay chain integration
Phase 3: Advanced Actions (Q3 2026)
- DeFi integrations (Uniswap, Aave)
- NFT minting automation
- DAO governance voting
Phase 4: Decentralization (Q4 2026)
- Decentralized worker network
- Staking for executors
- On-chain governance
π Acknowledgments
Autometa wouldn't exist without:
- Polkadot & Moonbeam - For building the infrastructure that makes this possible
- Moonbase Alpha Testnet - For providing a robust testing environment
- OpenZeppelin - For secure smart contract libraries
- The Web3 Community - For constant feedback and support
Built on Polkadot's Moonbeam parachain
Automating the future of Web3, one workflow at a time.
Built With
- apis
- asgi
- cloud-services
- databases
- eslint
- evm
- fastapi-0.115
- frameworks
- hardhat
- lucide
- moonbase-alpha
- moonbase-alpha-rpc
- moonbeam
- moonbeam-faucet
- moonscan-explorer
- next.js-15
- node.js-20
- npm
- openzeppelin-contracts
- platforms
- polkadot-parachain
- postcss
- pydantic-2.10
- python
- python-3.10
- radix-ui
- rainbowkit
- react-19
- redis-5.2
- shadcn/ui
- solidity-0.8.20
- tailwind-css-3.4
- turbopack
- typescript-5.6
- uvicorn
- venv
- viem-2.39
- wagmi
- web3
- web3.py-7.6
Log in or sign up for Devpost to join the conversation.