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.

note: aloni is built on pump.fun's creator fee program. every swap on the bonding curve or canonical PumpSwap pool automatically sends fees to the creator vault PDA.

# Architecture

the aloni stack consists of four primary modules:

moduleresponsibilitytech
agent-coreLLM evaluation, pitch scoring, winner selectionNode.js, Claude API
vault-monitorwatches creator vault PDA balance via RPC@solana/web3.js
cycle-manager60m timer, state machine, tx signingNode.js
api-serverREST endpoints for pitch submission & statusExpress

# Quickstart

install
# 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
.env configuration
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.

agent-core/evaluate.ts
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.

vault-monitor/index.ts
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.

cycle-manager/cycle.ts
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:

criterionweightdescription
originality30%has this argument been made before in the current cycle?
conviction25%does the user genuinely believe their pitch?
creativity25%is the pitch surprising, funny, or memorable?
engagement20%did the user create a genuine back-and-forth exchange?
anti-gaming: aloni detects and penalizes copy-paste submissions, automated pitches, and attempts to manipulate scoring through volume. quality over quantity.

# 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

POST /api/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

GET /api/status
{
  "cycle": 1847,
  "state": "collecting",
  "remaining_seconds": 2847,
  "vault_balance_sol": 2.847,
  "pitches_this_cycle": 23,
  "agent_online": true
}

# API: History

GET /api/history?limit=5
{
  "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..."
    }
  ]
}