aloni documentation
technical reference for the aloni agent, on-chain integration, and API.
# Overview
aloni is an autonomous Solana agent that collects pump.fun creator fees and redistributes them to users who submit the most compelling pitches. the agent evaluates submissions using an LLM and transfers the full vault balance to the winner every 60 minutes.
# Architecture
the aloni stack consists of four primary modules:
| module | responsibility | tech |
|---|---|---|
agent-core | LLM evaluation, pitch scoring, winner selection | Node.js, Claude API |
vault-monitor | watches creator vault PDA balance via RPC | @solana/web3.js |
cycle-manager | 60m timer, state machine, tx signing | Node.js |
api-server | REST endpoints for pitch submission & status | Express |
# Quickstart
# clone the agent git clone https://github.com/aloni-agent/aloni.git cd aloni # install dependencies npm install # configure environment cp .env.example .env # edit .env with your RPC url, creator keypair, and API keys # start the agent npm run start
SOLANA_RPC_URL="https://api.mainnet-beta.solana.com" CREATOR_KEYPAIR="[your-keypair-json]" ANTHROPIC_API_KEY="sk-ant-..." CYCLE_DURATION_MS=3600000 PORT=3000
# Agent Engine
the agent engine is the brain of aloni. it receives user pitches, maintains conversation context, and runs the evaluation at the end of each cycle.
import Anthropic from '@anthropic-ai/sdk'; const client = new Anthropic(); interface Pitch { userId: string; walletAddress: string; messages: { role: 'user' | 'agent'; content: string }[]; score?: number; } export async function evaluatePitches(pitches: Pitch[]): Promise<Pitch | null> { if (pitches.length === 0) return null; const summaries = pitches.map((p, i) => `pitch ${i + 1} (${p.walletAddress.slice(0,8)}...):\n${p.messages.map(m => m.content).join('\n')}` ).join('\n---\n'); const response = await client.messages.create({ model: 'claude-sonnet-4-6', max_tokens: 1024, system: `you are aloni, an AI agent that evaluates pitches. score each pitch 1-100 based on originality, conviction, creativity, and engagement. return the index of the winner.`, messages: [{ role: 'user', content: summaries }], }); // parse winner index from response const winnerIdx = parseWinnerIndex(response); return pitches[winnerIdx] ?? pitches[0]; }
# Vault & Fees
the vault monitor tracks the creator vault PDA balance in real-time and exposes it to the cycle manager and API.
import { Connection, PublicKey } from '@solana/web3.js'; const PUMP_PROGRAM = new PublicKey('6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P'); export function getCreatorVaultPDA(creator: PublicKey): PublicKey { const [pda] = PublicKey.findProgramAddressSync( [Buffer.from('creator-vault'), creator.toBuffer()], PUMP_PROGRAM ); return pda; } export async function getVaultBalance( connection: Connection, creatorVault: PublicKey ): Promise<number> { const balance = await connection.getBalance(creatorVault); return balance / 1e9; // lamports → SOL }
# Cycle Manager
manages the 60-minute reward cycles, handles state transitions, and triggers fee collection.
type CycleState = 'collecting' | 'evaluating' | 'distributing'; class CycleManager { state: CycleState = 'collecting'; pitches: Pitch[] = []; cycleStart: number = Date.now(); async tick() { const elapsed = Date.now() - this.cycleStart; if (elapsed >= 3600000 && this.state === 'collecting') { this.state = 'evaluating'; const winner = await evaluatePitches(this.pitches); if (winner) { this.state = 'distributing'; const tx = await collectAndTransfer(winner.walletAddress); await announceWinner(winner, tx); } // reset this.pitches = []; this.cycleStart = Date.now(); this.state = 'collecting'; } } }
# Pitch Evaluation
aloni's evaluation criteria and scoring weights:
| criterion | weight | description |
|---|---|---|
| originality | 30% | has this argument been made before in the current cycle? |
| conviction | 25% | does the user genuinely believe their pitch? |
| creativity | 25% | is the pitch surprising, funny, or memorable? |
| engagement | 20% | did the user create a genuine back-and-forth exchange? |
# Creator Vault PDA
the creator vault is a PDA derived from the bonding curve's creator field:
// rust definition from pump program #[account( mut, seeds = [ b"creator-vault", bonding_curve.creator.as_ref() ], bump )] pub creator_vault: AccountInfo<'info>,
# collectCreatorFee()
the instruction that transfers accumulated fees from the vault to a destination wallet. requires the creator's signature.
// collect and transfer fees to winner async function collectAndTransfer(destination: string) { const ix = await program.methods .collectCreatorFee() .accounts({ creator: creatorKeypair.publicKey, creatorVault: vaultPDA, destination: new PublicKey(destination), systemProgram: SystemProgram.programId, }) .instruction(); const tx = new Transaction().add(ix); const sig = await sendAndConfirmTransaction(connection, tx, [creatorKeypair]); return sig; }
# Fee Tiers
pump.fun uses dynamic fee tiers based on market cap. the fee rate applied to each swap depends on the current bonding curve market cap:
// market cap calculation for bonding curves function bondingCurveMarketCap({ mintSupply, virtualSolReserves, virtualTokenReserves }) { return virtualSolReserves .mul(mintSupply) .div(virtualTokenReserves); } // fee tier lookup function calculateFeeTier({ feeTiers, marketCap }) { for (const tier of feeTiers.slice().reverse()) { if (marketCap.gte(tier.marketCapLamportsThreshold)) { return tier.fees; } } return feeTiers[0].fees; }
# API: Submit Pitch
{
"wallet": "35Ez8VCXF4DkWPfBmqLZxaNt2jw2qvtN2jSPLkgojjqT",
"message": "here's why i deserve the fees..."
}
// response
{
"id": "pitch_1710782400000",
"reply": "interesting. you're in my top 5 right now...",
"cycle_remaining": 2847
}
# API: Cycle Status
{
"cycle": 1847,
"state": "collecting",
"remaining_seconds": 2847,
"vault_balance_sol": 2.847,
"pitches_this_cycle": 23,
"agent_online": true
}
# API: History
{
"cycles": [
{
"cycle": 1846,
"winner": "7xKXtg...2mNq",
"amount_sol": 3.214,
"tx": "5UxNrE...kP9w",
"pitch_count": 31,
"winning_pitch": "i taught my grandma to use phantom..."
}
]
}