Pump.fun Livestream
Stream a local video to Pump.fun's livestream player for an existing Pump.fun coin. The skill is plug-and-play: you provide the coin mint address and a local video path, the orchestrator handles auth, transcoding, and publishing.
Triggers
- "stream this video to pump.fun"
- "loop voice on the pump.fun livestream"
- "go live on pump", "broadcast on pump"
- "pump.fun livestream", "pumpfun stream", "live on pump.fun"
- "say X on pump.fun stream" → use
pump_say(TTS audio) - "write X / display X / show X on pump.fun stream" → use
pump_caption(visible text overlay) - "post X in pump.fun chat" → use
pump_chat(chat panel)
How it works (architecture)
agent's Solana wallet ─sign─► /auth/login ─JWT─► all subsequent calls
│
▼
/livestreams/create-livestream (mint) ──► stream record
│
▼
/livestreams/livekit/token/host (mint, creator) ──► LiveKit JWT
│
▼
ffmpeg(local.mp4 → raw.h264) ──► lk room join --publish ──► LiveKit room
│
▼
pump.fun live page
Pump.fun does not run their own video stack — they're a thin wrapper around LiveKit Cloud. The skill does NOT mint a coin, trade, or move SOL; it only operates on a coin the agent already owns.
Prerequisites
1. The agent's coin mint
The agent's pump.fun coin mint address must be stored in the vault as
pumpfun_coin_mint. EloPhanto's existing pump.fun coin:
elophanto vault set pumpfun_coin_mint BwUgJBQffm4HM49W7nsMphStJm4DbA5stuo4w7iwpump
2. Where to drop videos
The agent's workspace has a dedicated folder for streamable videos:
<agent.workspace>/livestream_videos/
For EloPhanto: /Users/0xroyce/agents/elophanto/livestream_videos/
Drop any .mp4, .mov, .webm, etc. into that folder, then call the
tool with just the filename — no need to type the full path:
{"action": "start", "video": "elephant-trailer.mp4"}
// → resolves to /Users/0xroyce/agents/elophanto/livestream_videos/elephant-trailer.mp4
Absolute paths still work and bypass the lookup:
{"action": "start", "video": "/Users/0xroyce/Desktop/clip.mp4"}
3. System binaries
Two CLI binaries must be on PATH. Verify with:
which ffmpeg && ffmpeg -version | head -1
which lk && lk --version
Install if missing:
| OS | ffmpeg | lk (LiveKit CLI) |
|---|---|---|
| macOS | brew install ffmpeg |
brew install livekit-cli |
| Linux | apt-get install ffmpeg |
Download from https://github.com/livekit/livekit-cli/releases (pick the right lk_linux_* binary, chmod +x, move to /usr/local/bin/lk) |
4. Solana wallet (already in the vault)
The skill reads the agent's existing keypair from solana_wallet_private_key
(set automatically when the wallet was created). No extra setup.
4. Vault password
Both auth and orchestrator scripts need VAULT_PASSWORD in the
environment to decrypt the wallet. The agent's running session already
has it; pass it through when invoking the script via shell_execute.
Workflow
From chat (preferred — uses the pump_livestream native tool)
The agent calls a single tool with one of these actions:
{"action": "address"}
// → {wallet: "..."} — show which wallet would sign auth
{"action": "login"}
// → forces a fresh JWT exchange (debugging only — auth happens
// automatically on every other call)
{"action": "start", "video": "/abs/path/to/video.mp4"}
// → uses pumpfun_coin_mint from vault. Returns
// {status: "started", pid, started_at, log_file, ...}
{"action": "start", "mint": "BwUg...pump", "video": "...", "fps": 24}
// → explicit mint and frame rate
{"action": "start", "video": "trailer.mp4", "loop": true}
// → resolves to <workspace>/livestream_videos/trailer.mp4 and
// restarts the publisher every time the video ends. Stop with
// {"action": "stop"}. Optional "max_iterations": N caps the loop.
{"action": "status"}
// → {status: "running" | "exited" | "not_running", ...}
{"action": "stop"}
// → SIGTERMs the publisher, removes transcoded .h264, clears state
The tool reads vault keys directly (no env var roundtrip) and runs in
the agent's running Python process. The actual lk publisher is
spawned as a detached subprocess so the chat returns immediately;
poll status to track the stream.
From shell (debugging — needs VAULT_PASSWORD)
cd /path/to/EloPhanto
VAULT_PASSWORD=$VAULT_PASSWORD python skills/pumpfun-livestream/scripts/pump_livestream.py \
start \
$(elophanto vault get pumpfun_coin_mint) \
/path/to/video.mp4
This will:
- Sign a login message with the agent's wallet, exchange it for a
pump.fun JWT (cached in the vault as
pumpfun_jwt). - POST
/livestreams/create-livestreamwith the mint (idempotent — 409 "already exists" is silently tolerated). - GET
/livestreams/livekit/token/hostfor a LiveKit JWT. - ffmpeg-transcode the input to raw H.264 (
video.h264next to it). - Spawn
lk room join --publish video.h264 --exit-after-publishas a detached subprocess. - Persist process state to
~/.elophanto/livestream-state/<mint>.jsonso the agent canstatus/stoplater.
The script returns immediately with {status: "started", pid, ...}.
The actual stream runs in the background until the video ends.
Status check
python skills/pumpfun-livestream/scripts/pump_livestream.py status <mint>
Returns one of:
running— publisher subprocess alive, stream is liveexited— process died (checklog_filefield for ffmpeg/lk output)not_running— no record at all
Stop a stream
python skills/pumpfun-livestream/scripts/pump_livestream.py stop <mint>
SIGTERMs the publisher (with a SIGKILL fallback after 3s), removes the
transcoded .h264 (unless --keep-h264 was set on start), and clears
state.
Lower-level commands
For debugging or partial flows:
# Just check or refresh the cached pump.fun JWT
python skills/pumpfun-livestream/scripts/pump_auth.py token
# Force a fresh login
python skills/pumpfun-livestream/scripts/pump_auth.py login
# Just register the stream record (no publishing)
python skills/pumpfun-livestream/scripts/pump_livestream.py create <mint>
# Just transcode (no streaming)
python skills/pumpfun-livestream/scripts/pump_livestream.py transcode \
in.mp4 out.h264
# Just fetch a fresh host LiveKit token
python skills/pumpfun-livestream/scripts/pump_livestream.py token <mint>
Tuning
| Concern | Knob |
|---|---|
| Frame rate | --fps 30 (default 30; lower = smaller files, fewer dropped frames) |
| LiveKit cluster | --livekit-url wss://... or LIVEKIT_URL env. Defaults to wss://pump-prod-tg2x9b6r.livekit.cloud. Override only if you've inspected pump.fun's frontend and confirmed a different cluster URL. |
| Keep transcoded file | --keep-h264 — useful for re-publishing without re-encoding |
| Skip create | --skip-create — when the stream record already exists and you just want to publish again |
What's intentionally NOT included
- Audio. The orchestrator strips audio (
-anin ffmpeg). Audio publishing requires a second LiveKit track and the codec must be Opus (.ogg). Easy to add later: re-run ffmpeg to producevideo.opus, pass both files tolk room join --publish(it accepts repeated--publishflags). - Live chat ingest. Pump.fun's chat is a separate API surface
(likely a websocket on the same
frontend-api-v3.pump.funhost). Not part of v1 — see follow-up notes below. - Auto-restart on disconnect. If the LiveKit publisher dies
mid-video,
statuswill showexitedand the agent must decide whether to restart. No supervisor loop. - Multi-coin / multi-stream concurrency. State is keyed by mint, so
in theory two different mints can stream concurrently. Same mint
twice = the second
startreturns{status: "already_running"}.
Live captions — pump_caption
Set or clear an on-screen text overlay on the voice-mode stream. ffmpeg's drawtext filter reloads the file every frame (~33 ms latency) so the caption updates as soon as the agent writes it.
{"action": "set", "text": "$ELO live · supply locked · CA: BwUg…pump"}
{"action": "set", "text": "answering: yes, the agent runs the chat itself.\nproof: tx 0xabc…"}
{"action": "clear"}
{"action": "current"} // returns whatever's on screen right now
Pair with the scheduler / heartbeat to rotate messages — price
tick, recent trade, FAQ answer, scrolling proof line. Pairs with
pump_say so viewers with sound off can still read what the agent
is "saying". Voice mode only — video-mode streams render the file
contents but it's not on the encode path.
Live chat — pump_chat
The chat panel that runs alongside the video uses Pump.fun's
livechat Socket.IO server at wss://livechat.pump.fun (path
/socket.io/). Authentication: the same JWT cookie minted by
/auth/login, passed in both the Cookie header and the Socket.IO
auth payload.
The agent calls a single tool from chat:
{"action": "say", "text": "gm — agent live, supply locked, no presale"}
// → posts to the agent's coin chat. Returns {posted: true, id: "<msg-uuid>"}
{"action": "say", "text": "yep — local agent streaming itself", "reply_to_id": "<msg-uuid>"}
// → threaded reply to a viewer's question
{"action": "history", "limit": 50}
// → returns recent messages (id, username, text, timestamp) so the
// agent can react to viewer questions
Same vault, same wallet, same coin mint as pump_livestream — no
extra setup. Use heartbeat or scheduled tasks to drip "proof"
messages periodically; don't loop in tight intervals. Marked
DESTRUCTIVE since posts are public under the agent's coin name.
Common errors
| Error | Cause | Fix |
|---|---|---|
VAULT_PASSWORD env var required |
Script invoked without the agent's vault password in env | Pass VAULT_PASSWORD=$VAULT_PASSWORD when calling via shell_execute |
Pump.fun login failed (401) |
Auth message format is rejected | Inspect pump.fun's web frontend network tab during a real login; tweak _build_message() in pump_auth.py to match |
'ffmpeg' not found on PATH |
Missing system binary | See Prerequisites table above |
'lk' not found on PATH |
Missing system binary | See Prerequisites table above |
LiveKit token endpoint returned no token |
Wrong JWT or stream record not yet created for that mint | Run pump_auth.py login first; then pump_livestream.py create <mint> |
start succeeds but pump.fun shows no stream |
Default LiveKit URL doesn't match pump.fun's actual cluster | Inspect pump.fun's network tab during a manual stream; pass real URL via --livekit-url |
exited status with empty video on pump.fun |
ffmpeg ran but the codec/container wasn't accepted by LiveKit | Check log_file from status output; usually a codec issue |
Source layout
skills/pumpfun-livestream/
├── SKILL.md # this file — agent-facing playbook
└── scripts/
├── pump_auth.py # wallet → JWT, cached in vault
└── pump_livestream.py # create / token / transcode / start / stop / status
Both scripts are pure-Python, depend only on httpx, solders,
base58 (already pinned in EloPhanto), and the system ffmpeg + lk
binaries.
Verify
- A real RPC/SDK call was issued (mainnet, devnet, or local validator) and the response payload is captured in the transcript, not just paraphrased
- Every transaction was simulated (
simulateTransactionor equivalent) before any signing/sending step; simulation logs are attached - For any signed/sent transaction, the resulting signature is recorded and confirmed on chain (status returned by
getSignatureStatusesor an explorer URL) - Slippage, priority-fee, and compute-unit limits were set explicitly with concrete numeric values, not left to library defaults
- Account addresses, mints, and program IDs used in the run match the documented pumpfun-livestream addresses for the targeted cluster (no mainnet/devnet mix-up)
- Failure path was exercised at least once (insufficient balance, stale oracle, expired blockhash, etc.) and the agent's error handling produced a human-readable message