What you need
| What for | Required | |
|---|---|---|
| A server | Linux with Node.js 22.13 or newer, or Docker. 1 CPU and 1 GB of memory run it; building the site wants about 2 GB. | yes |
| A domain with HTTPS | The site, link previews on X and Telegram, Privy. | yes |
| A Solana RPC | Every fact is read through it. It must answer getProgramAccounts and transaction history (e.g. Helius, Triton, QuickNode). | yes |
| A Claude API key | The chronicler. From console.anthropic.com. | yes |
| A little SOL | For the memo signer: about 0.000005 SOL per chapter; 0.05 SOL lasts thousands of chapters. | yes, to carve |
| A Privy App ID | Sign-in with email or X, with an embedded Solana wallet. | no |
| X API keys | The agent posts each chapter on your X account. | no |
| A Telegram bot and channel | The agent posts each chapter in your channel. | no |
| Your token’s address | Shown as the only official CA; holder perks. | no |
Install
- 1
Get the code and install
shellcd /opt/obelis npm ci
- 2
Write the settings
It asks for the domain, the Claude key, the model, the RPC, Privy and the token, makesshellnpm run setup
APP_SECRETand a fresh memo signer wallet, and writes.env.local(an older one is kept as.env.local.bak). Or copy.env.exampleto.env.localand fill it in. - 3
Check everything
shellnpm run doctor
- 4
Build
shellnpm run build
- 5
Run the site and the worker
Two processes: the site (npm start, port 3000) and the agent (npm run worker). Run exactly one worker. Ready-made units are indeploy/: systemd (obelis-web.service,obelis-worker.service), pm2 (ecosystem.config.cjs) and nginx with HTTPS (nginx.conf), or use Docker.
Every setting
All settings live in .env.local on the server. Only NEXT_PUBLIC_* values reach the browser, and they are built into the site: change one, then build again. The others are read when the processes start: change one, then restart them.
The site and its links
| Setting | What it does | Read by |
|---|---|---|
| NEXT_PUBLIC_SITE_URLhttps://obelis.example | The public address of the site, with https. Used in links, previews, feeds and the sign-in message. | site + browser |
| APP_SECRET | Signs sign-in sessions. A long random string (openssl rand -hex 32). Without it sessions reset on every restart. | site |
| NEXT_PUBLIC_X_URLhttps://x.com/your_handle | Your X account: shown in the header, footer and on the agent page; share links mention it. | site + browser |
| NEXT_PUBLIC_X_HANDLEyour_handle | The handle alone also works (without @). | site + browser |
| NEXT_PUBLIC_TELEGRAM_URLhttps://t.me/your_channel | Your Telegram channel’s public link. | site + browser |
| NEXT_PUBLIC_SOURCE_URL | Where your code is published, if it is. Shown on the agent page. | site + browser |
Sign-in
| Setting | What it does | Read by |
|---|---|---|
| NEXT_PUBLIC_PRIVY_APP_ID | Privy App ID. Empty: Solana wallets only. See Privy. | site + browser |
Solana
| Setting | What it does | Read by |
|---|---|---|
| SOLANA_RPC_URLhttps://mainnet.helius-rpc.com/?api-key=… | The RPC every fact is read and every memo is sent through. | both |
| HISTORY_LIMIT | Transactions read per reading, at most (500). | worker |
The chronicler
| Setting | What it does | Read by |
|---|---|---|
| ANTHROPIC_API_KEYsk-ant-… | The Claude API key. Only the worker reads it; it is never logged or sent to the browser. | worker |
| AGENT_MODEL | claude-opus-5-5 (default, the best writing), claude-sonnet-5-5 (about half the price) or claude-haiku-5-5 (the cheapest). | worker |
| AGENT_EFFORT | low, medium (default), high, xhigh, max, or default for the model’s own. | worker |
| AGENT_FALLBACKS | default: when a safety classifier declines, the API may retry on another Claude model. off: no fallback. | worker |
| AGENT_MAX_TURNS | Drafts per chapter, at most (5). | worker |
| AGENT_MAX_TOKENS | Room for thinking plus the chapter (16000). | worker |
| AGENT_CHAPTER_MAX_USD | Spend per chapter, at most ($0.30). | worker |
| AGENT_DAILY_USD | Spend per UTC day for the whole site ($20). | both |
| AGENT_PRICES{"claude-opus-5-5":{"in":4,"out":20}} | Override the price table, as JSON, if your prices differ. | worker |
| AGENT_DRY_RUN | 1: a scripted test chronicler instead of Claude, no key, no cost, clearly labelled on the site. For testing only. | worker |
| ANTHROPIC_BASE_URL | Only if you reach the Claude API through a gateway. | worker |
Chronicles
| Setting | What it does | Read by |
|---|---|---|
| CHAPTER_EVERY_HOURS | How often each chronicle is read (24). | both |
| BIG_BUY_SOL | A buy of at least this many SOL is a large buy (5). | both |
| CHRONICLE_FOR | wallets (default), holders or creators: who may begin a chronicle. | site |
| CHRONICLES_PER_WALLET_PER_DAY | Chronicles one wallet may begin a day (3). | site |
| MAX_CHRONICLES | Chronicles being read, at most (500). | site |
Carving
| Setting | What it does | Read by |
|---|---|---|
| MEMO_SIGNER_KEY | The memo signer’s secret key: base58, or the JSON array solana-keygen writes. Empty: no carving. | worker |
| MEMO_SIGNER_ADDRESS | Its public address, so the site can check memo signers even while the worker is offline. | site |
| MEMO_LOW_SOL | Below this balance the site and /api/health warn (0.01). | both |
| MEMO_PROGRAMMemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr | The SPL Memo program (leave as it is). | both |
Posting chapters
| Setting | What it does | Read by |
|---|---|---|
| X_API_KEY, X_API_SECRET | Your X app’s API key and secret (OAuth 1.0a consumer keys). See X. | worker |
| X_ACCESS_TOKEN, X_ACCESS_SECRET | The access token and secret of the account that posts, made after the app has Read and Write permission. | worker |
| X_POST_CHAPTERS | 0 turns X posting off without removing the keys. | worker |
| TELEGRAM_BOT_TOKEN123456:ABC… | The bot from @BotFather (also used by the optional CA bot below). | both |
| TELEGRAM_CHANNEL_ID@your_channel | The channel: @channelname, or the -100… id of a private one. | worker |
| TELEGRAM_POST_CHAPTERS | 0 turns Telegram posting off. | worker |
| ANNOUNCE_PER_DAY | Posts per UTC day on each, at most (15). | both |
Token
| Setting | What it does | Read by |
|---|---|---|
| TOKEN_CA | Your token’s mint: the only official address the site shows. Empty: “CA pending”. | site |
| TOKEN_SYMBOL | Its ticker (OBELIS). | site |
| HOLDER_MIN_TOKENS | How many tokens make a holder (with CHRONICLE_FOR=holders). | site |
| SWAP_WIDGET | 1 shows Jupiter’s swap, locked to TOKEN_CA; 0 hides it. | site |
| TOKEN_MARKET_DATA | 0 turns off the price and volume from DexScreener. | site |
| SWAP_CSP_CONNECT | Extra hosts the swap widget may connect to, if Jupiter needs new ones. | site |
The optional CA bot
| Setting | What it does | Read by |
|---|---|---|
| TELEGRAM_ADMIN_ID | Your Telegram user id: while TOKEN_CA is empty, you can send the CA to the bot and the site shows it. | site |
| TELEGRAM_WEBHOOK_SECRET | A random string; scripts/set-webhook.mjs registers the webhook with it. | site |
Storage
| Setting | What it does | Read by |
|---|---|---|
| OBELIS_DATA | Where the database, the coin images and the CA file live (./data). Back it up. | both |
Privy
- 1
Create an app
At dashboard.privy.io ↗, create an app. Its App ID is in the app’s settings; put it inNEXT_PUBLIC_PRIVY_APP_ID. - 2
Allow your domain
In Configuration → App settings → Domains, add your site’s origin exactly as inNEXT_PUBLIC_SITE_URL(https://…). Add http://localhost:3000 too if you test locally. - 3
Turn on the login methods
In the login methods, turn on Email, X (under socials) and External wallets with Solana. The site asks for exactly these three; one that is off in the dashboard shows “This login option is turned off”. - 4
Solana wallets
Make sure Solana is on for embedded wallets. The site asks Privy to create one for users who log in with email or X and have no wallet. - 5
Build again
The App ID is built into the site:npm run build, then restart.npm run doctorchecks that it looks right.
npm run privy:check tests that against a build with your App ID.Claude
- 1
Get a key
At console.anthropic.com ↗ → Settings → API keys. Put it inANTHROPIC_API_KEYon the server. - 2
Add credit and set a limit
Add credit in Billing, and set a monthly spend limit there too; AGENT_DAILY_USD is the site’s own limit on top of it. - 3
Try one chapter
npm run agent:testwrites one test chapter with your key and settings, and shows its cost.npm run doctor -- --livedoes it as part of the checks.
What it costs depends on the model, the effort and how much happens: a quiet day costs nothing; a chapter is usually a few cents on Opus, less on Sonnet, a fraction of a cent on Haiku. The agent page shows the real average per chapter once it runs.
The memo signer
- —
npm run setupmakes a new wallet for it and writes bothMEMO_SIGNER_KEYandMEMO_SIGNER_ADDRESS. Use a wallet that does nothing else. - —Send it a little SOL (0.05 is plenty). The agent page and /api/health show its balance and warn below MEMO_LOW_SOL.
- —It only ever signs memos. If its key leaked, the worst anyone could do is write memos in its name: make a new one, and the agent page shows the new address.
X
- 1
A developer account
At developer.x.com ↗, sign in with the account that will post, and create a project and an app. - 2
Read and Write
In the app’s User authentication settings, set the app permissions to Read and Write (the type can be Web App; the callback and website can be your site’s address). - 3
Keys and tokens
In Keys and tokens: the API Key and Secret go inX_API_KEYandX_API_SECRET. Then generate the Access Token and Secret (after setting Read and Write, or they will be read-only) and put them inX_ACCESS_TOKENandX_ACCESS_SECRET. - 4
Restart the worker
The worker log says “posts to x”.npm run doctor -- --livesigns in to X with the keys and names the account that will post.
Posting limits
X API plans limit how many posts an app may make. KeepANNOUNCE_PER_DAY under your plan’s daily allowance; past it, chapters are still on the site and in RSS.Telegram
- 1
A bot
In Telegram, ask @BotFather ↗ for a new bot (/newbot). Its token goes inTELEGRAM_BOT_TOKEN. - 2
A channel
Create a public channel (or use yours). Add the bot as an administrator with the right to post messages. - 3
Tell the worker
TELEGRAM_CHANNEL_ID=@your_channel(a private channel: its -100… id). AndNEXT_PUBLIC_TELEGRAM_URL=https://t.me/your_channelso the site links it. - 4
Restart
The worker log says “posts to telegram”.npm run doctor -- --liveasks Telegram whether the bot may post in the channel.
Docker
npm run setup # or write .env.local by hand docker compose up -d --build # the site on 127.0.0.1:3000 and the worker docker compose exec web npm run doctor docker compose logs -f worker
The site and the worker share one volume (/data) with the database and the coin images. The image is built with .env.local as a build secret (the NEXT_PUBLIC_* values are built in); it is not copied into the image. Put nginx or Caddy with HTTPS in front (deploy/nginx.conf).
Checks
| Command | What it checks |
|---|---|
| npm run doctor | Every setting, the keys’ format, the RPC (and that it answers what the facts need), the memo signer and its balance, the build, the worker, the site, X and Telegram settings. |
| npm run doctor -- --live | Also: one real chapter on Claude (a few cents), the X sign-in, the Telegram channel. |
| npm run agent:test | One test chapter with your Claude settings, with its cost. |
| npm test | The unit tests, with pump.fun’s real programs on a local chain. |
| GET /api/health | For an uptime monitor: ok, the worker, and problems to fix. |
Operating
| npm run admin -- … | What it does |
|---|---|
| status | Chronicles due, today’s spend, the worker, the memo signer. |
| pause / resume | Stop or restart all reading (the site stays up). |
| check <mint> | Read this chronicle now. |
| hide <mint> / show <mint> | Take a chronicle off the site, or bring it back. Its memos stay on chain. |
| hide-chapter <id> / show-chapter <id> | The same for one chapter. |
| stop <mint> / start <mint> | Stop or restart reading one chronicle; its chapters stay. |
| ban <wallet> [reason] / unban <wallet> | Block a wallet from beginning chronicles. |
- —Back up
OBELIS_DATA: the SQLite database (copy it with the site and worker stopped, or live withsqlite3 data/obelis.db ".backup obelis-backup.db") and the coin images next to it. - —To update: get the new code,
npm ci,npm run build, restart both. The database upgrades itself on start. - —Keep .env.local readable by the service user only (chmod 600). Never put a secret in a NEXT_PUBLIC_ setting.