FIP: Channels on Snapchain #276
Replies: 12 comments 26 replies
-
|
first |
Beta Was this translation helpful? Give feedback.
-
|
this is great work and we're excited to see it through. we've got many more questions but the first two things that jump out:
put another way this is a question about whether to bet the protocol on coinbase or on the evm, no?
is it worth considering a sliding scale based on name length, similar to ens? |
Beta Was this translation helpful? Give feedback.
-
|
Pins? On protocol with this FIP |
Beta Was this translation helpful? Give feedback.
-
I'd lean L1 for neutrality and durability. Not that Base is likely to disappear tomorrow, but Ethereum is more likely to remain for the long term. Base could change protocols (as has already been demonstrated), among other things.
$25 seems wildly high to register a namespace but I think it's worth looking at how other registrars apply their pricing methodology. Look at ENS, which is ~$5 by default but for more desirable names, the price increases. Taking over an ENS domain also works in a dutch auction style. Some similar methodology would likely work here. Default to low-cost, increase for high-value names, penalize for takeover with degrading cost basis. Or, if we want to look at web2, where a domain can be acquired for as little as $0.99 but, again, where a high-value namespace is priced accordingly. I don't think $25 as the default makes sense and acts more like a deterrent. Dynamic pricing should be considered.
See above. Following ENS methodology here would make the most sense. -- Disclaimer: apologies for any mistaken nomenclature. Doing my best to use the internal Farcaster vernacular. |
Beta Was this translation helpful? Give feedback.
-
This is great as long as there is first-class support for smart contract signatures. Enabling builder/experimentation flexibility is a big part of the value here, so it's important that arbitrary smart contracts be able to own and operate channels. |
Beta Was this translation helpful? Give feedback.
-
Stock registry here is a redeployed registry, right? I think it would be cool if channels lived in a subdomain of either chains most canonical name system, e.g. .farcaster.eth or .farcaster.base.eth I could see communities more easily building treasuries this way, "our community accepts donations at football.farcaster.eth" and having day-1 interop w/ the rest of the ENS ecosystem. There's already example implementations of a subdomain registrar we could piggy-back off of: https://github.com/ensdomains/ens-contracts/tree/feature/subdomain-registrar/contracts/subdomainregistrar |
Beta Was this translation helpful? Give feedback.
-
|
IMO, hitching wagons to base like this is a bad move – it adds even more chain indexing requirements, bloats shard 0 (which ideally should be light as it needs to be present for the other shards when validating messages), doesn't solve squatting (it just moves the responsibility to fighting it out over basenames). Not to mention, base's contribution to farcaster was onboarding a million $BASE airdrop farmers then abandoning farcaster entirely. If we're going to accept the cost of expanding shard 0 anyway, we should natively add channels to the protocol as a first class object. Channels should have unique identifiers by hash, but display names should be non-unique, which removes the name squatting issue entirely. |
Beta Was this translation helpful? Give feedback.
-
|
Does renaming mean changing identity? |
Beta Was this translation helpful? Give feedback.
-
|
I agree with the idea that channels should be identified by a smart contract address, not by display name, something like an ERC-725 smart contract account would be a good example. One of the problems of the original channels experiment was people getting very high-value names and churning where the namespace becomes useless for everyone. For example, there can be multiple competing communities for /sf and the one with the highest Neynar score (by sum over members) should get top rank in the search results, or get to own the URL redirect on the primary client. It's also worth pointing out Bluesky is developing communities, so they will likely do a similar architecture where communities will be identified via a DID. |
Beta Was this translation helpful? Give feedback.
-
There's a few issues with binding all actions to the channel_id which is derived from name:
Also it's not clear to me what the $25 registration is accomplishing. What is the goal? If the goal is to setup channels for success, why is $25 the best way to do this? If names are purely cosmetic (IMO that's how it should be) then the $25 is less important -- I can start a community, and if it grows big enough to justify buying an ENS for it, then great. Also, if the name is cosmetic, why not just use ENS (or even wei/gwei)? Is it important for this to be Farcaster's core competency? |
Beta Was this translation helpful? Give feedback.
-
|
gm eggman here, excuse the blank profile (I moved to gitlab like a hipster) Firstly, extremely happy to see channels come back! General thoughts:
Last tidbit, I'd be against any many-to-one relationship on names pointing at channels which has been mentioned as a detractor for the hash(channel) type storage. For all the good parts like having A couple of difficult issues to tackle while wanting to avoid centralised components. |
Beta Was this translation helpful? Give feedback.
-
|
Regarding Owner address → fid resolution, what is the user experience for someone who has verified an eth address to multiple FIDs, and they want the ownership to show as a different FID then the default of last verified? is it this:
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Abstract
Channels today live entirely inside clients and their backends. They are not part of the protocol. Ownership, configuration, membership, moderator roles, and moderation all sit in one operator's database, so any client, indexer, or app that wants to build on channels has to call that operator's private API instead of reading protocol state everyone can see.
This proposal moves the existing channel feature set onto snapchain, so channels work the way casts, links, and reactions do today: anyone can read and write them without going through a specific operator. It standardizes what channels already do rather than adding new behavior, which also gives the community a base to propose new channel-management models against through the FIP process. It has two halves:
Put together, a channel's full state can be rebuilt from onchain events and signed messages, with no operator in the trusted path. Anchoring ownership in a name registry buys one more thing the current setup lacks: names expire. That is a squatting control (see Motivation), and it touches channel management only. Casting into a channel is never affected by expiry, because casting is not gated by channel state at all (see Rationale).
Motivation
Channels are one of Farcaster's most-used surfaces, but they are not part of the protocol. They live in client and backend databases. Three things motivate moving them:
Anchoring ownership in a name registry has a secondary benefit: expiry. Channels already cost $25 to create, but that is a one-time payment with no expiry, so a squatter can hold a name forever. A registry charges for registration and renewal and lets names lapse, so an unused name returns to the pool. That turns the fee people already pay into a squatting control without adding a new fee. (Amount and term are in Open Questions.) Like everything about expiry, this affects management, not casting.
This proposal deliberately does not enforce casting permissions at the protocol level (see Rationale). It standardizes membership and ownership, and leaves posting policy where it is today, in clients and backends.
Specification
1. Onchain channel registry
Channel ownership is recorded by a stock, unmodified Basenames deployment. The spec assumes Base (chain id
8453) as its working default, but the target chain — Ethereum L1 vs Base — is itself an open question (see Open Questions); the Basenames contracts deploy unchanged on either:BaseRegistrar— an ERC-721 registrar; each channel is a token. Standard expiry + grace period.RegistrarController— paidregister/renewpriced in ETH via anIPriceOracle.No contract fork is required. Target chain, registration amount, term, and grace period are deployment parameters (to be finalized, see Open Questions).
Channel key ↔ name. The existing channel key format
^[0-9a-z][0-9a-z-]{0,15}$maps directly onto a registrar label. The registrar events carry the plaintextname, so no reverse resolution is needed.Channel onchain identity. A channel is addressable as
chain://eip155:<chainId>/erc721:<registrar>/<tokenId>(working defaultchainId = 8453, pending the chain decision), tying channel identity to its ownership source.Relevant stock events observed by nodes:
where
label = keccak256(name)andtokenId = uint256(label).2. Registration ingestion as an onchain event
Nodes ingest channel registration exactly as they ingest tier purchases today (
TierPurchaseis the precedent: a paid, time-bounded event on Base). A new onchain event type is added:The body mirrors
IdRegisterEventBody(a stable-identifier +event_typediscriminator union): a raw ERC-721Transfercarries onlytokenId, solabel(= keccak256(name) = tokenId = channel_id) is the one key present on every event kind and is what joins a transfer or renewal back to its channel. Field numbers are frozen once shipped.Owner address → fid resolution. The registrar events carry an address, not an fid, so ownership is resolved to an fid at read time: a channel-owner read joins the stored
owner_addressagainst verification state to find the fid that currently controls the address. Resolution follows last-write-wins — the most recentVerificationAddfor the address wins, and if that verification is removed, ownership falls back to the next-best verifier or to unresolved. Resolution is deliberately not done at ingest: verifications live on their author's shard and never reach shard 0 (where the register event lands), so a merging node can't see enough to attribute an fid deterministically. The event therefore stores the address with no fid (fid = 0), and attribution is computed fresh on each read. Until some fid has verified the owner address the channel resolves to no owner and channel administration isn't possible; it becomes administrable the moment a verification links the address to an fid.Ownership-change events. Because attribution is read-time, an ownership flip has no merge of its own to hang an event on. So that consumers don't have to join the verification and channel streams themselves, nodes emit a
HubEventwhenever a channel's resolved owner changes — whether from aVerificationAdd/VerificationRemovethat moves the binding, or an onchainTransfer. Shuttle- and indexer-type consumers can then track ownership on a single stream.Transfer. An ERC-721
Transferarrives as aCHANNEL_REGISTERevent withevent_type = TRANSFER, carrying the newowner_addressand thelabel(it has no name); it updates the stored owner address, the owner fid re-resolves at read time, and an ownership-changeHubEventis emitted.Expiry / renewal. A
RENEWevent (event_type = RENEW, fromNameRenewed) extends the storedexpiry. Nodes follow ENS semantics: active → grace → released. Once a channel is past expiry (in grace), permissioned management writes freeze (§4) until a renewal reactivates it. Casting is not affected. Because casting is never gated by channel state (§3), a lapsed channel keeps taking casts exactly like an active one; only owner and moderator actions freeze.3. Channel messages
Every message references a channel by a 32-byte identifier:
ChannelUpdate{ channel_id, name, description, image_url, header, rules, casting_mode, membership_mode }ChannelMember{ channel_id, fid, action }ChannelFollow{ channel_id }(+ remove)ChannelPin{ channel_id, cast_hash }(+ unpin)ChannelModerate{ channel_id, cast_hash, action: HIDE|UNHIDE }ChannelUpdatecarries two mode axes:casting_mode ∈ {EVERYONE, MEMBERS_ONLY, RECOMMENDED}is advisory metadata, not enforced by the protocol. The protocol accepts casts into a channel whatever the mode says; clients and backends honor it, as they do today.RECOMMENDEDtells clients to apply their own discretion and moderation tooling (labels, user scores) when deciding what to surface.membership_mode ∈ {OPEN, APPROVAL}is protocol-relevant. It decides whether a self-authored join (ChannelMember{ADD_MEMBER, fid=self}) is valid: allowed onOPEN, rejected onAPPROVAL.ChannelMemberaction enum:{ADD_MEMBER, REMOVE_MEMBER, ADD_MODERATOR, REMOVE_MODERATOR, BAN, UNBAN}.4. CRDT semantics and authority
ChannelUpdate: last-write-wins by Farcaster timestamp (tie broken by lower message hash). Owner-only.ChannelMember: add-wins for membership, and a ban beats a membership add. Materializes toChannelMemberState { fid, is_member, is_moderator, is_banned, member_since, moderator_since, banned_at }.ChannelFollow/Unfollow: add/remove-set CRDT identical toLinkAdd/LinkRemove.ChannelPin: single pin per channel; a new pin replaces the previous.ChannelModerate: cast-level HIDE/UNHIDE; advisory to clients.Authority matrix (enforced when validating owner/moderator messages, on the read path):
ADD_MEMBER/REMOVE_MEMBER,BAN/UNBANADD_MODERATOR/REMOVE_MODERATORADD_MEMBERon anOPENchannelChannelUpdate,ChannelPin,ChannelModerateOwner-dependent authority relies on owner attribution, which is resolved at read time (§2), so these checks run on the validation/read path rather than at merge. The materialized member/moderator set in shard 0 supplies the moderator side of each check. While a channel's registration is expired (in grace), the permissioned writes above are frozen. Self-directed follows still work, and so does casting (posting is never gated by channel state, so expiry can't stop casts).
5. Storage placement (sharding)
Channel state lives only in shard 0, and is not replayed:
ChannelUpdate,ChannelMember(including self-join),ChannelPin, andChannelModerateroute to shard 0, next to the ownership event (§2) and the materialized owner/moderator set. Co-locating them puts the channel config, the register event, and the owner/moderator set the authority checks read in one place; owner attribution itself is resolved at read time (§2), and the shard-0 state supplies the config and moderator set those checks consult.casting_modeis advisory (§3), nothing on a fid shard needs it at cast-merge time. "Channels by fid" reads come from a by-fid index kept within shard 0.ChannelFollowandUnfolloware self-authored and need no channel config to validate, so they stay on the author's own shard and reuse the existing Link CRDT machinery.ChannelMember(including self-join) is routed to shard 0 rather than the author's shard because validating a self-join againstmembership_moderequires the channel configuration, which lives only in shard 0.6. Backfill / migration
Existing channels are seeded at migration:
CHANNEL_REGISTERevents through the normal ingestion path — no special route.Rationale
Why a name registry for ownership (and the expiry it brings)? Ownership has to be anchored somewhere the protocol can read. An ERC-721 name registry fits: a channel key is a name, and it matches the way Farcaster already establishes ownership onchain. The expiry that comes with it closes a real gap. Channels already cost $25, but a one-time fee does nothing to stop squatting, since a squatter is happy to pay once and hold a key forever. Charge that same fee on a renewal cycle and an unused name eventually lapses back into the pool. Basenames gives us all of this (priced registration, renewal, expiry, grace) in audited contracts, which is why we use it unmodified instead of building our own. Again, this is a side benefit, not the point of the proposal.
Why keep create/transfer onchain? An ERC-721 name registry is a good ownership primitive: it matches the pattern Farcaster already uses to establish ownership onchain, and its built-in expiry adds squatting enforcement, all from audited stock contracts.
Why not enforce casting permission on-protocol? To enforce
MEMBERS_ONLYat cast-merge, every fid shard that processes a cast would have to consult channel membership, which is channel-keyed state on shard 0. That means either cross-shard reads or replicating membership onto every shard, on the hot cast path. The payoff is low: casting policy is a client and backend concern today, and it works. Makingcasting_modeadvisory removes the only thing on a fid shard that would ever need channel state, which is what makes the no-replay, shard-0-only design work.Why shard 0 with no replay, rather than mirror-to-all-shards? Channel management state is low-volume and keyed by channel. Co-locating it in shard 0 puts the config and owner/moderator set in one place for the authority checks, which run on the read/validation path (owner attribution is resolved at read time). Replaying to fid shards would only pay off if something there consumed it, i.e. if casting were enforced on-protocol, and it isn't. Full mirroring would multiply writes by the shard count with no reader to justify the cost.
Why read-time address→fid resolution? The owner address isn't guaranteed to be linked to an fid at registration, and verifications don't live on the shard where the register event lands, so a node can't attribute an fid at ingest. Resolving at read time — last-write-wins over the address's verifications — keeps attribution correct as verifications change, and lets a channel registered before its owner is verified attribute automatically once the link appears.
Backwards Compatibility
casting_modeis defined with room to add tiers (e.g.RECOMMENDED) without a breaking change; message-type numbers are allocated from the next free protocol values.subscribed,favoritePosition) and display/ranking fields remain client/backend concerns and are explicitly out of scope for the channel record.Security Considerations
Open Questions
eip155:8453) as its working default. Base aligns with theTierPurchaseprecedent — paid, time-bounded events are already ingested from Base — and keeps registration/renewal gas cheap, which matters because gas price is much lower. Ethereum L1 is the alternative: more credibly neutral for a naming namespace. The Basenames contracts deploy on either chain unchanged; on Base they are native, on L1 they sit under an ENS name. snapchain already watches L1, OP, and Base, so ingestion is not the deciding factor — the trade is Base's precedent plus low gas cost against L1's neutrality. The choice fixes thechain://eip155:<id>identity and the fee currency.GRACE_PERIODis 90 days, and we plan to keep it. During grace, permissioned management is frozen (§4) while casting continues (§3); calling it out here so the timescale for a lapsed channel to free back up is explicit for reviewers.Beta Was this translation helpful? Give feedback.
All reactions