Neutrofill is an automated filler bot that processes inbound transaction requests from CompactX and executes them if deemed profitable. It maintains a server-side wallet and automatically submits transactions that meet profitability criteria.
- Accepts inbound POST requests for transaction execution
- Real-time ETH price monitoring across multiple chains via CoinGecko
- Profitability analysis before transaction submission
- Support for multiple chains (Ethereum, Optimism, Base, Unichain)
- WebSocket support for real-time transaction status updates
- Automated cross-chain token rebalancing via bridging using the Across Protocol
- Automated single-chain token rebalancing via swaps using the Uniswap API
A hosted version can be accessed at neutrofill.com — note that this service is meant to serve as a reference for demonstration purposes and no guarantees about its uptime or reliability are provided.
This project is mostly meant to serve as a starting point until more mature standards like the Open Intents Framework have fully integrated with The Compact.
- Node.js 18+
- npm 9+
-
Clone the repository:
git clone https://github.com/Uniswap/Neutrofill.git cd Neutrofill
-
Install dependencies:
npm install
-
Set up Git hooks:
npx husky init
-
Copy
.env.example
to.env
and configure your environment:cp .env.example .env
-
Start the development server with hot reload:
npm run dev
-
Run type checking:
npm run typecheck
-
Run the test suite:
npm test
-
Format and lint code:
npm run fix
Build both server and client:
npm run build
This will:
- Compile TypeScript server code to
dist/server/
- Build React client to
dist/client/
Start the production server in production mode:
npm start
Or start the production server with debug logs enabled:
npm run start:debug
Neutrofill includes an automated cross-chain token rebalancing mechanism that helps maintain target token percentages across multiple blockchain networks. This maintatins sufficient funds on each supported chain to operate efficiently.
- Configurable target percentages for each chain
- Token-specific rebalancing priorities
- Automatic detection of chains that need funds
- Cooldown periods between rebalancing operations
- Minimum and maximum rebalance amounts
- Transaction tracking and status monitoring
- Event-based notifications for rebalance operations
For cross-chain bridging:
- The system monitors token balances across all supported chains
- When a chain's balance falls below its configured threshold, a rebalance operation is triggered
- The system identifies a source chain with excess funds
- Funds are transferred from the source chain to the destination chain using the Across Protocol
- The operation is tracked until completion
For single-chain swapping, neutrofill uses the Uniswap API. Note that all swaps are performed on Unichain to allow for best execution with lowg gas costs.
Rebalancing is configured in src/server/config/rebalance.ts
. You can customize:
- Target percentage for each chain / token
- Trigger thresholds for rebalancing
- Token priorities for rebalancing
- Global settings like minimum/maximum amounts and cooldown periods
The following environment variables are required:
PORT
: Server port (default: 3000)PRIVATE_KEY
: Private key for the wallet that will execute transactionsRPC_URL_MAINNET
: Ethereum mainnet RPC URLRPC_URL_OPTIMISM
: Optimism RPC URLRPC_URL_BASE
: Base RPC URLRPC_URL_UNICHAIN
: Unichain RPC URLCOINGECKO_API_KEY
: CoinGecko API keyUNISWAP_API_KEY
: Uniswap API keyCOMPACT_INDEXER
: Graphql indexing service (e.g. Ponder node) for The Compact
- Mainnet (Chain ID: 1)
- Optimism (Chain ID: 10)
- Base (Chain ID: 8453)
- Unichain (Chain ID: 130)
Submit a transaction for potential execution. Generally, this will be relayed from a service like disseminator — reach out to be added as a webhook.
Request body must conform to the BroadcastRequest
schema which includes:
chainId
: Chain ID where the resource lock resides (numeric string or hex)compact
: Compact message objectarbiter
: Ethereum addresssponsor
: Ethereum addressnonce
: 32-byte hex stringexpires
: Numeric string or hexid
: Numeric string or hexamount
: Numeric string or hexmandate
: Mandate object
sponsorSignature
: 64-byte hex string, '0x', or null (no signature indicates an onchain registration)allocatorSignature
: 64-byte hex stringcontext
: Context objectdispensation
: Numeric string or hexdispensationUSD
: String (can include $ prefix)spotOutputAmount
: Numeric string or hexquoteOutputAmountDirect
: Numeric string or hexquoteOutputAmountNet
: Numeric string or hexdeltaAmount
: (optional) Numeric string or hex (can be negative)slippageBips
: (optional) Number between 0-10000witnessTypeString
: StringwitnessHash
: 32-byte hex stringclaimHash
: (optional) 32-byte hex string
claimHash
: (optional) 32-byte hex string
The mandate
object represents parameters related to the fill chain and contains:
chainId
: Chain ID where the resource lock resides (positive number)tribunal
: Ethereum addressrecipient
: Ethereum addressexpires
: Numeric string or hextoken
: Ethereum addressminimumAmount
: Numeric string or hexbaselinePriorityFee
: Numeric string or hexscalingFactor
: Numeric string or hexsalt
: 32-byte hex string
Be sure that any ECDSA signatures are encoded in their "compact" representation using EIP-2098.
Check server health status.
Response:
{
status: "ok",
timestamp: number // Unix timestamp in seconds
}
Connect to /ws
for real-time transaction status updates. Note: WebSocket endpoint does not support CORS - connections must originate from the same origin.
Message Types:
- Connection Status (Server -> Client):
{
type: "connection",
status: "connected" | "disconnected",
timestamp: string // ISO timestamp
}
- Heartbeat (Bidirectional):
{
type: "ping" | "pong",
timestamp: string // ISO timestamp
}
- Fill Request Status (Server -> Client):
{
type: "fillRequest",
success: boolean,
request: {
chainId: string,
compact: {
arbiter: string,
sponsor: string,
nonce: string,
expires: string,
id: string,
amount: string,
mandate: {
chainId: number,
tribunal: string,
recipient: string,
expires: string,
token: string,
minimumAmount: string,
baselinePriorityFee: string,
scalingFactor: string,
salt: string
}
},
sponsorSignature: string | null,
allocatorSignature: string,
context: {
dispensation: string,
dispensationUSD: string,
spotOutputAmount: string,
quoteOutputAmountDirect: string,
quoteOutputAmountNet: string,
deltaAmount?: string,
slippageBips?: number,
witnessTypeString: string,
witnessHash: string,
claimHash?: string
}
},
error?: string, // Present only if success is false
transactionHash?: string, // Present only if success is true
details?: { // Present only if success is true
dispensationUSD: number,
gasCostUSD: number,
netProfitUSD: number,
minProfitUSD: number
}
}
- Price Update (Server -> Client):
{
type: "price_update",
chainId: number,
price: string, // Price in USD
timestamp: string // ISO timestamp
}
- Account Update (Server -> Client):
{
type: "account_update",
account: string,
chainId: number,
balances: Record<string, string>, // Token balances in wei
timestamp: string // ISO timestamp
}
- Error (Server -> Client):
{
type: "error",
code: string,
message: string,
timestamp: string // ISO timestamp
}
- TypeScript for type safety
- Biome for linting and formatting
- Jest for testing
- Husky for Git hooks
- lint-staged for pre-commit checks
The project includes a setup script for deploying to a cloud server with automatic HTTPS configuration using Let's Encrypt.
- A domain name pointing to your server (A record)
- Ubuntu-based cloud server
- SSH access to the server
- SSH into your server:
ssh user@your-server
- Clone the repository:
git clone https://github.com/Uniswap/neutrofill.git
cd neutrofill
- Run the setup script with your domain and IP:
./scripts/setup-server.sh your-domain.com your-server-ip
For example:
./scripts/setup-server.sh neutrofill.com 167.172.1.91
The script will:
- Install required dependencies (Node.js, nginx, certbot)
- Set up the project in /opt/neutrofill
- Configure nginx with WebSocket support
- Set up SSL certificates with Let's Encrypt
- Create and enable a systemd service
- Start the server
Monitor the server status:
sudo systemctl status neutrofill
View server logs:
sudo journalctl -u neutrofill -f
Test WebSocket connection:
wscat -c wss://your-domain.com/ws
Test broadcast endpoint:
curl -X POST https://your-domain.com/broadcast \
-H "Content-Type: application/json" \
-d '{ ... your payload ... }'
Test health endpoint:
curl https://your-domain.com/health
MIT