Outpost

Your own node and client for Farcaster.

Outpost rents a server, keeps your own copy of Farcaster's data on it, and gives you a client that reads from that copy and posts through it. Three commands, and no API key from anyone.

  • MIT licence
  • No API keys
  • Every output from a real run
  1. Step 1./outpost provision

    Rents a server on DigitalOcean or Hetzner, billed by the hour.

  2. Step 2./outpost node

    Starts a read node that keeps the whole network's data.

  3. Step 3./outpost client

    Opens a client in your browser that reads and posts through your node.

a day while the server exists
~$10
or free as a member of a society node
for the first sync
~6 h
the network's data, on your disk
557 GB
API keys needed
0

What Outpost is

Farcaster is a social network whose posts, likes and follows are public data. That data is kept by Snapchain, a network of computers called nodes. A small, fixed set of them, the validators, agree on the order of new messages. Anyone can run a read node: it keeps a full copy of the data and answers questions about it.

Outpost sets up a read node on a server you rent, and runs a client on your computer that reads from that node and posts through it. Besides the server, you need no API key and no paid service.

What it costs

About $10 a day

About $0.41 an hour on DigitalOcean at October 2026 list prices: $0.25 for the server (8 vCPU, 32 GB RAM) and about $0.16 for its 1.2 TB disk. Billed by the hour until you run ./outpost destroy. Hetzner is also supported; check its prices before you start.

How long it takes

About 6 hours

For the first sync on our test server: 1 hour 43 minutes to load a snapshot of the network, then 4 hours 11 minutes to catch up on the blocks since. The client works once loading is done. The newest casts appear as the node catches up.

Your copy of the network

557 GB

The whole network's data, on your server, on 18 October 2026.

API keys needed

None

No API key, no Ethereum RPC and no paid service besides the server.

What you end up with

  • A read node on your server holding the whole network's data.
  • A web client at http://127.0.0.1:8381 that shows your following feed and can cast, reply, like, recast and follow.
  • An app key that can cast, react and follow, and nothing else.
The web client on a phone: the feed of FID 99 with app filter buttons (warpcast 64, own key 15, neynar 12 and more), a Show pictures box, and casts with their authors' profile pictures. The web client on a laptop: two casts from the feed of FID 37, each with its author's profile picture and an attached image shown in the feed.
The web client, reading mainnet from a Snapchain read node on 22 October 2026: the following feeds of FID 99 (phone) and FID 37 (laptop). Open either image for full size.

Who it is for

Farcaster users who are at ease in a terminal, and developers who want to see the protocol from the node up. You will rent a server and run a few commands. A community can share one node: its members use it from a browser and pay nothing (step 8).

How the pieces fit

Your computer

Your browser opens 127.0.0.1:8381.

The Outpost client runs here. It holds your app key and signs what you post.

Your server

A Snapchain read node with a full copy of the network's data on a 1.2 TB disk.

Its API is closed to the internet. The client reaches it through the tunnel.

The rest of Farcaster

Other nodes, and the validators that put messages into blocks.

Account and key events from the Optimism chain arrive inside those blocks.

  • Reading. The client asks your node, which already holds the network's data.
  • Posting. The client signs the message with your app key and sends it through the tunnel to your node. The node checks it and passes it to the validators over gossip. Outpost then reads the message back from your node before it says "confirmed".
  • Open ports. Two, on the server: 22 for SSH and 3382/udp for gossip.

What is different about it

  • Your own node. Your feed is read from a node you run, and your posts go out through it. Step 2
  • A narrow app key. The key the client holds can cast, react and follow. It cannot change your profile or your verified addresses, and it expires 30 days after its last use. Step 3
  • Revoking needs no recovery phrase. The key signs its own removal, from the client or with ./outpost uninstall. Step 9
  • A feed in time order, labelled by app. Your following feed is the latest casts of everyone you follow, merged by time. Each cast shows the app that posted it, and you can show only one app or hide it. Step 5
  • Every message is checked. Before showing a message, the client checks its hash and signature, and that the key that signed it belongs to the author. What it shows is rebuilt from the signed bytes, so a node cannot alter a message or slip anything into it unnoticed. Step 8
  • One node for a whole society. Members of a society can share one node. Each gets their own access token and rate limit, and their app keys stay in their own browsers. Step 8
  • Societies as signed files. A charter lists a society's members, which apps and channels count, and its code of conduct. Load one to read the society's feed. Leaving is one tap, and a census of its activity is computed on your node. Step 7

Limits, and what is not tested yet

Read these before you rent a server, so nothing surprises you halfway.

Limits

  • Outpost does not create accounts or buy storage. Bring an account that already has storage.
  • The recovery-phrase login is for test accounts. With a real account, use the wallet button.
  • An account made in the Farcaster app keeps its custody key inside that app, so it cannot connect to Outpost without exporting its recovery phrase, which we advise against. Try Outpost with a test account, and keep using the Farcaster app for your main one.
  • Casts you write are text up to 1,024 bytes with up to 4 links. There is no image upload, an @name you type stays plain text, and there is no notifications view.
  • Keys that people signed for themselves all show as "own key" in the app labels (step 5).
  • A society host can see what members read. TRUST.md lists what a host can and cannot do.
  • The node costs money for as long as it exists, whether or not you use the client.

Not tested on a real server yet

  • Hetzner. Our real runs used DigitalOcean; the Hetzner commands have only run against a simulated Hetzner in our tests.
  • ./outpost client --phone, which serves the client to your phone over HTTPS. It has only run in our tests, against a simulated cloud provider.
  • Windows. Use WSL; we have not tested it there.

About this project

Code: github.com/Spider333/outpost, MIT licence. Snapchain is GPL-3.0; Outpost only runs its official Docker image and contains none of its code.

How it was made: an AI coding assistant helped write the code and this page. People ran it and tested it. Every output on this page comes from a real run; where it was run somewhere other than a fresh node, the page says so.

Ran it yourself? The voucher checklist has a list to fill in as you go and a draft cast.

Quickstart

Budget about 6 hours for the first sync and about $10 a day for as long as the server exists. Five commands take you from nothing to your own node and back to nothing.

You need:

  • A DigitalOcean or Hetzner account with its command-line tool logged in (doctl or hcloud), or any Ubuntu 24.04 server with root SSH and at least 1 TB of disk.
  • Node.js 20 or newer.
  • macOS or Linux. On Windows, use WSL; we have not tested it there.
  • A test Farcaster account that already has storage. Read the warning below first.
First time with DigitalOcean or Hetzner? Set up the command-line tool

DigitalOcean. Make an account at digitalocean.com and add a payment method. Install doctl (install guide; on a Mac, brew install doctl). In the control panel, open API, choose Generate New Token, give it Full Access and a 30-day expiry (the watchdog and ./outpost destroy still need it later; if it has expired, make a new one and run doctl auth init again). Then log in with it:

doctl auth init
# check: prints your account e-mail and droplet limit
doctl account get

Hetzner. Make an account at hetzner.com/cloud and create a project. Install hcloud (install guide; on a Mac, brew install hcloud). In the project, open Security, then API tokens, and generate a token with Read & Write access. Then log in with it:

hcloud context create outpost
# check: lists your servers (an empty list is fine)
hcloud server list

Both commands ask you to paste the token. Keep it out of chats, casts and AI assistants, and delete it in the control panel when you are done.

Before you paste a recovery phrase

A recovery phrase is the custody key of an account. Whoever has it can transfer the account, add or remove every app key, and change the username. Pasting it into the wrong program is like running rm -rf / as root: there is no undo.

  • Use a test account. Never your main account.
  • Only paste a phrase into software whose code you have read, or whose author you trust. That includes this one.
  • Prefer the wallet button. With it, the phrase never leaves your wallet.
  • Revoke the app key when you are done: ./outpost uninstall, or "Revoke this key" in the client.

What Outpost does with a phrase: it derives the custody address in memory, signs one authorization for an app key that can only cast, react and follow and that expires 30 days after last use, then drops it. It never writes the phrase to disk and never sends it over the network. You can check this in src/core/account.ts and src/cli/main.ts.

git clone https://github.com/Spider333/outpost
cd outpost
# rent a server with a 1.2 TB disk (1 TB on Hetzner), billed by the hour
./outpost provision
# start a read node; the first sync took about 6 hours on our test server
./outpost node
# then open http://127.0.0.1:8381
./outpost client

When you are done:

# revoke the app key; no recovery phrase needed
./outpost uninstall
# delete the server and stop paying
./outpost destroy

Every command is safe to run twice. ./outpost provision --dry-run prints the exact provider commands without running them.

Just trying it? ./outpost provision --watchdog 24 deletes the server for you after 24 hours, or after 2 hours with no progress, so a forgotten server cannot keep billing you. It runs on your computer; ./outpost watchdog stop keeps the server.

With an AI coding assistant

Paste this into an assistant that can run commands on your computer, such as Claude Code, Codex or Cursor:

Help me run Outpost (https://github.com/Spider333/outpost). Read its README first. Then take me through provision, node and client one step at a time: before each command, say what it does and what it costs, and wait for my OK. I will type any cloud token or recovery phrase myself in my own terminal, never in this chat.

The assistant runs the same commands as above, so the same costs apply. Keep tokens and recovery phrases out of the chat: the assistant's provider would see them.

What you just did

You rented a server, started a read node on it that loaded the whole network's data, and opened a client on your computer that reads from that node through an SSH tunnel and posts through it. What you read came from your own copy of the network, not from anyone else's server. How the pieces fit draws it, and the tutorial below runs each command again with its real output.

Tutorial

Nine steps, from renting a server to deleting it. Every output is copied from a real run; when a run was not on a fresh node, the step says so. Concept boxes explain each idea where it first matters, and every term is also listed under Terms.

  1. 1Rent a serverOne big enough for the whole network, billed by the hour.
  2. 2Start a read nodeThe official Snapchain node; about 6 hours to sync the first time.
  3. 3Connect an accountGive the client an app key that can only cast, react and follow.
  4. 4Read and postYour following feed from your node, then cast, follow, like and recast.
  5. 5Filter by appSee which app posted each cast, and show or hide an app.
  6. 6What a keyless node cannot doGood to knowThree things that need an Ethereum RPC, and how to add one.
  7. 7Societies: a charter fileOptionalA signed file that turns the client into a society's client.
  8. 8Share your nodeOptionalLet a whole society use your node from a browser, for free.
  9. 9Clean upRevoke the key and delete the server so it stops billing.

1Rent a server

A node keeps a full copy of the network's data. On 18 October 2026 that was 557 GB once loaded, from a snapshot of about 400 GB compressed. Outpost streams the snapshot straight into the database, so it never needs room for both at once, and asks for a 1.2 TB volume to leave room to grow (--disk to change it). It also asks for 32 GB of RAM: there are reports of nodes with 16 GB crashing.

On Hetzner it asks for 1 TB, because new Hetzner accounts can hold at most 1024 GB of volumes. Hetzner is not tested on a real server yet: our real runs used DigitalOcean, and the Hetzner commands have only run against a simulated Hetzner in our tests. We expect a full node to fit in 1 TB but have not measured it there.

./outpost provision --provider do
# or: ./outpost provision --provider hetzner

Here is what it runs, shown with --dry-run (long lines and your home path shortened):

The first three lines give the price. Each line starting with + is one command sent to DigitalOcean.

DigitalOcean: droplet outpost-node (s-8vcpu-32gb-amd) in fra1 with a 1200 GB volume.
Billed by the hour while it exists: the volume about $0.16/h, plus the droplet
(s-8vcpu-32gb-amd is $0.25/h). October 2026 list prices; check yours.
+ doctl compute ssh-key import outpost-node-key --public-key-file /home/you/.ssh/id_ed25519.pub
+ doctl compute volume create outpost-node-data --region fra1 --size 1200GiB --fs-type ext4 --desc outpost\ snapchain\ data
+ doctl compute tag create outpost-node
+ doctl compute firewall create --name outpost-node-fw --tag-names outpost-node --inbound-rules protocol:tcp\,ports:22\,... protocol:udp\,ports:3382\,...
+ doctl compute droplet create outpost-node --region fra1 --size s-8vcpu-32gb-amd --image ubuntu-24-04-x64 --ssh-keys f8:bd:...:0a --user-data-file /tmp/tmp.6oiXwl8gsl --volumes \<volume-id\> --tag-name outpost-node --wait

The firewall opens two ports: 22 for SSH and 3382/udp for gossip, which is how your node talks to the rest of the network. The node's API ports stay closed. Your client reaches them through an SSH tunnel. This matters: by default, a node with an open API port lets anyone on the internet send messages through it.

Already have a server? Skip this step and run OUTPOST_HOST=<ip> ./outpost node. It needs Ubuntu 24.04, root SSH and the disk above.

2Start a read node

Read nodes and validators. Snapchain has two roles, run by the same program. Validators propose and vote on blocks; there is a fixed set of them. A read node, which is what you are about to start, follows the chain, keeps the full state and answers queries. It never votes. Anyone can run one, and it needs no key or paid service: account and key events from the Optimism chain arrive inside the blocks validators produce.

Read nodes also take writes. When your client sends a message, your node checks it, then passes it to validators over gossip. "Accepted" means accepted for relay, not yet in a block, so Outpost reads every message back from your node before it says "confirmed".

./outpost node
The line that matters: synced: every shard is within 0 blocks of the tip. Show the full output
setting up the read node on 142.93.168.228
[node-setup] data directory: /mnt/snap (1356 GB free)
[node-setup] starting snapchain 0.14.3 (read node)
 Container snapchain-snapchain-1  Running
 Container snapchain-statsd-1  Running
snapchain-snapchain-1 Up 9 hours
snapchain-statsd-1 Up 24 hours (healthy)
[node-setup] done. HTTP API on 127.0.0.1:3381 once the snapshot has loaded.

The node downloads a snapshot of the network (about 560 GB), loads it, then catches up
on the blocks since. On our test server loading took about 1 hour 45 minutes and
catching up about 4 hours more. The client works once loading is done; the newest casts
appear as it catches up. You can press Ctrl-C at any time; the node keeps going.
Check later with ./outpost status.
03:50 synced: every shard is within 0 blocks of the tip
next: ./outpost client

This is a second run on our already synced node, so it found the node running and returned at once. On a new server the last lines show download and loading progress until the node is synced.

This runs the official farcasterxyz/snapchain Docker image, pinned to version 0.14.3, with the official compose file. Outpost does not use the official installer script, because it reads from /dev/tty and so fails without a terminal, and because with no operator FID it exits before starting the node (issue #1030).

Our test server was a DigitalOcean one with 8 vCPU, 32 GB RAM and a 1.2 TB volume, on 18 to 19 October 2026: loading took 1 hour 43 minutes and catching up 4 hours 11 minutes. Snapchain's own loader took 6 hours 40 minutes to load on the same kind of server. Catching up takes longer the older the snapshot is. ./outpost status shows where it is:

node: loading snapshot: shard 1: 200 of 2672 chunks loaded, 20 GB on disk

Once it has caught up:

./outpost status

"Within 1 blocks of the tip" means your copy of every shard is at most one block behind the newest.

server: 142.93.168.228 (OUTPOST_HOST)
node: synced: every shard is within 1 blocks of the tip

Our node was set up before ./outpost provision existed, so we pointed Outpost at it with OUTPOST_HOST. A server made by ./outpost provision shows its provider and name on the first line instead.

3Connect an account

./outpost client
your node is reachable through an SSH tunnel on 127.0.0.1:13394 (no port opened on the server)
open http://127.0.0.1:8381 in your browser. Ctrl-C stops the client.
client: http://127.0.0.1:8381
node:   http://127.0.0.1:13394 (requests to /v1/* are forwarded there)

We ran it as OUTPOST_TUNNEL_PORT=13394 ./outpost client, because the default local port, 13381, was already in use on our machine. Yours will say 13381 unless you change it.

On a phone, run ./outpost client --phone instead. It serves the client from your server over HTTPS, using a free <ip>.sslip.io name so you need no domain, and puts a password in front of everything, including the node's API.

Not tested on a real server yet: --phone has only run in our tests, against a simulated cloud provider, so there is no real output to show here.

Open the address it prints. To post, choose Sign with a wallet if your account's custody address lives in a wallet, or Use a test account's recovery phrase. The command-line version is ./outpost cli login.

FID. Your account is a number, the Farcaster ID, registered on the Optimism chain. Usernames point to it; posts are signed for it.

Custody. One Ethereum address owns each FID: the custody address. Your recovery phrase is the private key for that address, at the standard path m/44'/60'/0'/0/0. Outpost derives the address, asks your node which FID belongs to it, and checks that the address is still that FID's custody before it signs anything. No Optimism RPC is needed for this; your node has the registry events.

Storage. The network only keeps messages for accounts that rent storage. One unit bought after July 2025 holds 100 casts, 200 reactions and 200 follows; when you go over, the oldest are pruned. Storage is bought onchain. Outpost does not buy it; it stops if the account has none.

App keys. You do not sign every post with the custody key. Instead the custody key authorizes an app key, an Ed25519 key the client holds. Since May 2026 this is a plain message to the network, KEY_ADD, with no Optimism transaction and no gas. Each key has:

  • scopes: the message types it may sign. Outpost asks for casts, reactions and follows only, so it cannot change your profile or verified addresses;
  • a TTL: it expires after 30 days without use (the maximum is 90);
  • a requesting app: the FID of the app that asked for it, signed by that app's custody key. That is how anyone can tell which app posted a cast.

Two details trip people up. The signature uses its own EIP-712 domain, Farcaster KeyAdd with chain ID 1, not the onchain key gateway's domain, so the older helper functions produce signatures the network rejects. And the deadline is in Farcaster time, seconds since 1 January 2021, not Unix time.

The outputs in this step and the next are from mainnet, with our test account (FID 3355358), on 18 October 2026. The warning printed before the first question is left out here, and the home path is shortened.

./outpost cli login

Before it asks you to sign, Outpost shows the custody address, the FID that address owns, its storage units and what the key may do.

Is this a test account, and do you accept the above? [y/N] y
Recovery phrase (input hidden):

custody address  0x15020a39b36cF852CDBD1aC036DF1f45ea4ccafa
FID              3355358
storage units    1
app key          can cast, react and follow; expires 30 days after last use
Sign the app key authorization for this FID? [y/N] y

app key 0xe0bb564ae0787b31ade0ac66856fcfb745b0eb98846e6370235332ca837cebde is live. Saved to ~/.config/outpost/appkey.json.
Your recovery phrase was not saved anywhere. Revoke this key any time with: ./outpost cli uninstall
./outpost cli whoami
FID 3355358, app key 0xe0bb564ae0787b31ade0ac66856fcfb745b0eb98846e6370235332ca837cebde
scopes: cast_add, cast_remove, reaction_add, reaction_remove, link_add, link_remove
expires: 2026-11-17T22:50:31.000Z unless used before then

Before any account is involved, you can check the signing code against your own node. This builds every message type Outpost uses with throwaway keys, asks the node to validate each one, and checks that two tampered messages are rejected. It sends nothing.

./outpost cli selftest
All 11 checks passed. Show the full output
PASS  KEY_ADD (gasless, scoped, 30-day TTL): node says valid=true, expected true
PASS  CAST_ADD: node says valid=true, expected true
PASS  CAST_REMOVE: node says valid=true, expected true
PASS  LINK_ADD follow: node says valid=true, expected true
PASS  LINK_REMOVE follow: node says valid=true, expected true
PASS  REACTION_ADD like: node says valid=true, expected true
PASS  REACTION_ADD recast: node says valid=true, expected true
PASS  REACTION_REMOVE like: node says valid=true, expected true
PASS  KEY_REMOVE (self-revoke): node says valid=true, expected true
PASS  CAST_ADD with text changed after signing: node says valid=false, expected false
PASS  KEY_ADD with TTL widened after signing: node says valid=false, expected false

all 11 checks passed against http://127.0.0.1:13392

This run was against our mainnet node, through the tunnel on port 13392. The node's validator checks signatures and message rules, not account state, so it cannot tell you whether a custody signature matches a real account. The unit tests (npm test) cover that with known test vectors.

4Read and post

Snapchain has no home timeline endpoint. A following feed is the list of accounts you follow (/v1/linksByFid), the latest casts of each (/v1/castsByFid), merged by time. That is one request per account you follow, which is fine on your own node.

./outpost cli feed --fid 99 --limit 3

Each cast takes three lines: time, author and the app that posted it; the text; the author's FID and the cast's hash. The last lines count casts per app.

2026-10-18 22:46  @catra.eth  via warpcast (app fid 9152)
  @higherathletics proof  [1 embed]
  9318:0xbe6ca325398da8036c64f40605447a4b965f4231
2026-10-18 22:43  @trish  via own key
  My cat just dragged 32 oz of perfectly cooked medium rare flank steak off a hot pan and into the bathroom. Any suggestions for dinner? 162g of protein gone 😭
  4877:0x00eb1eece902ca6cf186c0aedb4bce2dc5c829d6
2026-10-18 22:39  @pichi  via warpcast (app fid 9152)
  As always, @kenjiquest crushed this one as well. I will never tire of this use of AI as a creative outlet.
  234616:0xf873c07770c4758afd0e0ec9da5a7c876c602586

3 casts in 4.51 s from http://127.0.0.1:13392
    2  via warpcast (app fid 9152)
    1  via own key

This is the following feed of FID 99, read from our mainnet node on 18 October 2026. Any account's following feed is public, so you can read one without logging in.

./outpost cli cast "Outpost test cast 1: testing a self-hosted client"
./outpost cli follow 1
./outpost cli like 3355358 0x0fe046f163063a3200260f1fbec8338d871d2ef0
./outpost cli recast 3355358 0x0fe046f163063a3200260f1fbec8338d871d2ef0
cast: 0x0fe046f163063a3200260f1fbec8338d871d2ef0 (confirmed: your node returns it)
follow: 0x43e3c752147f247248d92577bdba8272ac2be313 (confirmed: your node returns it)
like: 0x7a0cf348a93aa9c6e56955e3d7f776830c80b0d0 (confirmed: your node returns it)
recast: 0x06fcb40fa0fbd70a3caa344afd4704b7bd5a046f (confirmed: your node returns it)

Each line is a message hash. "Confirmed" means Outpost read that exact message back from your node. We liked and recast our own test cast, and undid all three afterwards with unlike, unrecast and unfollow.

Messages. Every action is a message: a small protobuf with your FID, a timestamp, the action, a BLAKE3 hash of those bytes and an Ed25519 signature from the app key. A cast, a follow, a like and a recast are four message types. Nodes check the signature and that the key is allowed to sign that type, then store the message. Deleting a cast is another message that removes it.

5Filter by app

A cast does not say which app posted it. But it is signed by an app key, and every app key records the FID of the app that requested it. Outpost looks this up on your node for every author (/v1/signersByFid) and labels each cast. In the client, tap an app to show only its casts, again to hide them, again to reset. In the command line:

./outpost cli feed --fid 2 --limit 2 --exclude 9152

With app 9152 (warpcast) hidden, the feed shows casts from other apps and from people's own keys.

2026-10-18 22:06  @baseddesigner.eth  via puffinapp (app fid 3355363)
  testing
  196957:0x1fcb5aedc4a0a8ea2372b7f8a6e021400f67b29f
2026-10-18 21:16  @b05  via own key
  Amazon Discounts New Mac Mini by Up to $149, Mac Studio by $200 https://www.macrumors.com/2026/10/18/new-mac-mini-149/ via /feedme app by @b05
  196142:0x018d27bf1bff4e8e8cf6386b6c7e64cfd4c1bc59

2 casts in 4.04 s from http://127.0.0.1:13392
    1  via puffinapp (app fid 3355363)
    1  via own key
./outpost cli feed --fid 37 --limit 2 --include 16823

Only casts from app 16823 (fireflyapp) are left.

2026-10-17 05:50  @vitalik.eth  via fireflyapp (app fid 16823)
  Fun fact of the day: Bitcoin mining cost is split about 50/50 between compute capex and electricity. Meanwhile, AI inference is almost completely dominated by compute capex.  [2 embed]
  5650:0x9252885364ac7db01ac427e97dd246b7af619949
2026-10-15 03:39  @vitalik.eth  via fireflyapp (app fid 16823)
  Big congratulations to the @zksync airbender team for this milestone. At this point, I consider the battle for speed to be definitively won, and the top priority for zkEVMs should shift fully to secur  [1 embed]
  5650:0x2939942b565704c087d33fd818fac745551776e6

2 casts in 0.58 s from http://127.0.0.1:13392
    2  via fireflyapp (app fid 16823)

What this can and cannot tell you:

  • A key a user signed for themselves carries their own FID as the app. Tools that do this, including Outpost by default, all look the same: "own key".
  • A service that signs for many apps under one FID shows up as one app.
  • An app key copied into another program cannot be told apart.
  • The requesting app cannot be faked: the network checks the app's custody signature. But one developer can register several FIDs.

6What a keyless node cannot do

Your node runs without an Ethereum RPC endpoint. That is what makes it keyless, and it costs three things. It rejects, and so you cannot submit through it:

  • .eth usernames and other ENS names,
  • ENS username proofs,
  • address verifications for smart-contract wallets.

Casts, reactions, follows, profile fields, plain usernames, normal wallet verifications and app keys all work. To get the three back, add l1_rpc_url with any Ethereum mainnet RPC to the node's config.

7Societies: a charter file

Filtering by app is one rule. A society is a set of them, written down and signed: who the members are, which apps' posts count, which channels and keywords, and a link to the society's code of conduct. Outpost calls this a charter. It is one JSON file.

The repository has an example built from public data: the 24 accounts that posted most in the /network-states channel in the last 90 days. It is unofficial and names no issuer, because nobody from that channel signed it.

./outpost cli charter verify examples/network-states.charter.json
OK: "network-states channel (example)", 24 members, signed by 0x71bFF431251Afc3E941f6f606B9016Ff3660D6F4. It names no issuer FID, so only the address vouches for it

The signature covers every field. Change one member and the file is rejected:

jq '.members += [3]' examples/network-states.charter.json > tampered.charter.json
./outpost cli charter verify tampered.charter.json
REJECTED: the file was changed after it was signed (signature does not match its content)

If the charter names an issuer FID, Outpost also checks on your node that the signer is that account's custody address. Nobody else can sign for the society.

In the client, load a charter in the Society panel. The feed then shows the society's members and channels, through its rules. Join follows its members with your app key. Leave drops the lens. Your keys, follows and posts do not change, because they were never the society's to begin with. Leaving is one tap.

The census counts members, members who posted in the last 7 days, casts per day, and the apps members post from. It reads only your own node, so anyone with a node and the same file gets the same numbers.

./outpost cli charter census examples/network-states.charter.json
"network-states channel (example)"
census 2026-10-11 to 2026-10-18 (7 days), from your node
  members            24
  active (posted)    23
  casts              1071  (153 per day)
  apps members posted from:
     1023  warpcast (app fid 9152)
       32  own key
       16  fireflyapp (app fid 16823)
computed in 0.5 s

To check it, scripts/manual-census.sh counts the same thing with only curl and jq, none of Outpost's code. Run a few seconds later, it agreed:

scripts/manual-census.sh examples/network-states.charter.json 7
members 24
active 23
casts 1071
casts_per_day 153.0

A charter is a lens, not a wall. It changes what you see and whom you follow. It cannot stop anyone posting, and it hides nothing from anyone else.

8Share your node

A node costs money for every hour it runs, so it makes sense for a society to share one. In society mode your server stops being only yours: members of your society use it from their own browsers, with their own accounts, for free.

That means members trust you a little. Outpost keeps that trust small, in code anyone can read. Your server serves no page and no script, so you cannot change the client a member runs. Their app keys stay in their browsers, so you cannot post as them. Their client checks every message it reads, so you cannot alter or invent what they see. What you can still do, such as see what they read, is listed in TRUST.md.

./outpost society on
open (or close) 443/tcp in your server's firewall yourself; Outpost did not create this server.
[society] serving the node API at https://142-93-168-228.sslip.io to members only (no web page)

society node: https://142-93-168-228.sslip.io (the node API for members; no web page)
add a member: ./outpost members add <name>
the first request can take a minute while the certificate is issued.
turn it off with: ./outpost society off

Shortened: the lines that download the gate's container image are cut. Our node was set up before ./outpost provision existed, so we ran this with OUTPOST_HOST and opened 443/tcp in its firewall ourselves.

A gate now sits in front of the node. It lets a request through only with a member's token, only for reads and for posting, and only as fast as that member's rate limit allows. Add a member:

./outpost members add test1
invite link for test1 (shown once; send it privately, whoever has it can use the node as test1):
it works once society mode is on (./outpost society on). Remove with: ./outpost members remove test1

Shortened: the line with the link is cut, because it holds test1's token.

The link opens the client from GitHub Pages, built from this repository by a public workflow, and tells it which node to use. The token comes after #, so the browser never sends it to GitHub. Your server only ever stores a hash of it. ./outpost members remove test1 turns the token off at once.

Any member can check the gate from outside with curl. With test1's token in a file:

scripts/society-check.sh https://142-93-168-228.sslip.io token.txt
without a token:          401
with a wrong token:       401
with your token:          200
a page (/):               403
POST /v1/validateMessage: 403
DELETE /v1/info:          403
type of a reply:          application/json
300 reads at once (count, status, Retry-After):
 111 200 
 189 429 1

401 means no access without a valid token; 403 means the gate refuses everything except reads and posting; 429 means the member hit their rate limit (by default 20 reads a second, with bursts of 100) and should wait the number of seconds given.

The command-line client works against a society node too. It checks every message, and asks a second node (Farcaster's public one, unless you pick another) about keys that have no onchain record:

npm ci && npm run build
node dist/cli.js feed --fid 3 --limit 8 --node https://142-93-168-228.sslip.io --token-file token.txt
2026-10-19 08:48  @grin  via warpcast (app fid 9152)
  Marrakech goes hard  [1 embed]
  1970:0x4ca01b4642ff443661db6812a0ef49e0765aac1e
2026-10-19 02:26  @brixbounty  via own key  [signer confirmed by second node]
  18723:0x45e4db64566b2ba0b92a4ba4e1f08043f033f60b

8 casts in 7.74 s from https://142-93-168-228.sslip.io
    7  via warpcast (app fid 9152)
    1  via own key
warning: Your node sent 1 of 4160 messages that failed verification (1 altered); they are not shown.

Shortened: 6 of the 8 casts are cut.

"Signer confirmed by second node" means the cast was signed with a key added without an onchain transaction, and a second, independent node confirmed it is the author's. In our samples about 1 honest message in 10,000 is encoded in a way the client cannot rebuild exactly; it is dropped and counted with the rest, never shown as if it were fine.

./outpost society off stops the gate, and on a server Outpost made, closes 443/tcp.

9Clean up

./outpost uninstall
revoking the CLI's app key with the key itself (no recovery phrase needed)
revoked app key 0xe0bb564ae0787b31ade0ac66856fcfb745b0eb98846e6370235332ca837cebde for FID 3355358. No recovery phrase was needed.
deleted ~/.config/outpost/appkey.json
keys added in the web client are revoked from its 'Your app key' panel.

Casts the key signed stay on the network after it is revoked. (Checked on mainnet with Snapchain 0.14.3: our test cast is still there after its key is revoked.) Delete them first with ./outpost cli delete <hash> if you want them gone.

The key signs its own removal (KEY_REMOVE), so no recovery phrase is needed. If you never revoke it, it expires 30 days after its last use.

./outpost destroy

This deletes the server, the volume and the firewall after you type the server's name. Your account, follows and casts are not affected; they live on the network.

Allegiance: which network states you belong to

A Farcaster mini app and a public census. It lists network-state projects, shows which of them run an official Farcaster channel and how many members each has approved, and tells you which ones you belong to.

It adds no protocol of its own. Farcaster channels already have owners, moderators and members, and Allegiance reads that public data. Live since 22 October 2026.

Projects tracked

108

Network-state projects from Polystate's public directory, matched to Farcaster channels.

Official channel

1

Network School's /network-states: 9,972 followers, 190 approved members.

Not confirmed

12

A channel with the project's name exists, but the project has not confirmed it as theirs.

No channel yet

95

Counts as read on 22 October 2026. The census page always shows the latest.

Open it

  1. Inside Farcaster. Post or open the link allegiance.shy-surf-2fdf.workers.dev/app in a cast. It shows a See allegiances button that opens the mini app, which already knows your account.
  2. In a browser. Open the same link and look up any account by its username.
  3. The census. allegiance.shy-surf-2fdf.workers.dev lists every project, its channel, its status and its counts, with the change over 7 days.

What it shows you

  • Your network states. The network-state channels you own, moderate, are a member of, or follow.
  • Projects on Farcaster. Only channels confirmed as the project's own. Join opens the channel: follow it, and the owner or a moderator can invite you as a member.
  • Something to share. Share my allegiances drafts a cast that names official projects only.

Followers and members. Anyone can follow a Farcaster channel, so followers means interested. Members are approved: the channel's owner or a moderator invites you, and you accept.

How the numbers work

  • Official means it was checked that the project runs the channel.
  • Not confirmed means a channel with the project's name exists, but the project has not confirmed it as theirs. Many are run by fans, and their numbers say nothing about the project itself. They are shown by channel name, never as the project.
  • Counts come from public Farcaster channel data, read every 6 hours, with one snapshot a day for the 7-day change.

Anyone can recount. This asks Farcaster's public API for Network School's channel, with no key:

curl -s "https://api.farcaster.xyz/v1/channel?channelId=network-states" | jq '.result.channel | {followerCount, memberCount}'
{
  "followerCount": 9972,
  "memberCount": 190
}

Run on 22 October 2026 at 14:15 UTC. It matched the census at the time.

What it does not do yet

  • It counts Farcaster accounts, not verified people.
  • Only 1 of the 108 projects has a channel checked as official so far, so most rows say "not confirmed" or "no channel".
  • Allegiance is independent, and not affiliated with or endorsed by the projects listed. Run a project? The census page says how to confirm your channel, correct it, or remove your listing.

Terms

Every term used on this page, in one place. Where a tutorial step explains a term in more depth, the entry links to it.

App key
An Ed25519 key that signs your posts, so the custody key is not needed for each one. Outpost's can only cast, react and follow, and expires 30 days after its last use. Step 3.
Census
A count of a society's members, active members, casts per day and the apps they post from, computed from your node. Step 7.
Charter
Outpost's signed JSON file for a society: its members, which apps, channels and keywords count, and a link to its code of conduct. Step 7.
Custody address
The Ethereum address that owns an FID. Its private key comes from your recovery phrase. Step 3.
FID
Farcaster ID: the number that is your account, registered on the Optimism chain. Usernames point to it.
Gossip
How nodes pass messages and blocks to one another, over UDP port 3382.
KEY_ADD, KEY_REMOVE
The messages that add and remove an app key. Since May 2026 they go straight to the network, with no Optimism transaction and no gas.
Message
One action, such as a cast, a like or a follow, signed by an app key. Its hash identifies it; the commands print that hash. Step 4.
Optimism
The blockchain where FIDs are registered and owned. Its events reach your node inside the blocks validators produce, so the node needs no connection to Optimism.
Read node
A Snapchain node that keeps the full state and answers queries. It never votes on blocks, needs no key, and passes your messages on to validators. Anyone can run one. Step 2.
Recovery phrase
The words that hold an account's custody key. Whoever has them can transfer the account. Read the warning before you use one.
Scopes
The message types an app key may sign.
Shard
Snapchain splits its data into shards, each with its own chain of blocks. A node is synced when every shard is at or near the tip.
Snapchain
The network of nodes that stores Farcaster's data, and the program those nodes run. Outpost runs its official Docker image, version 0.14.3.
Snapshot
A compressed copy of the network's data. A new node loads it first, then catches up on the blocks since. On 18 October 2026 it was about 400 GB compressed and 557 GB once loaded.
SSH tunnel
An encrypted connection over SSH that makes a port on the server reachable as a port on your computer. Outpost uses one so the node's API never faces the internet. Your client reaches the node at 127.0.0.1:13381 by default.
Storage
Space an account rents onchain for its messages. One unit bought after July 2025 holds 100 casts, 200 reactions and 200 follows; past that, the oldest are pruned.
Tip
The newest block of a shard.
TTL
How long an app key lives without being used: 30 days for Outpost's keys, 90 at most.
Validator
One of the fixed set of Snapchain nodes that propose and vote on blocks.

Not affiliated with Farcaster, Snapchain or their maintainers.