Changelog
Methodology
- We are versioning according to semver.org
- We are currently in the "Initial development phase"
0.17.0
BREAKING CHANGE: Community membership (protocol 14.0.0)
Joining and leaving a community are signed messages now, so every node knows who is a member: communities you joined follow you to every device, a community's owner learns who joined, and member counts are real. See "Membership" in the protocol docs.
Breaking Changes
- Protocol versions bumped:
- Pubsub topics (roots/messages):
13.0.0 → 14.0.0 - Protocols (leaves/levels):
16.0.0 → 17.0.0
- Pubsub topics (roots/messages):
- New message types
joinandleave, signed asMembership(string type,string community,uint256 timestamp), withcommunitya community's ID. Nodes accept one only if the community exists in a version older than the message, like a post to it, whatever the signer's earlier joins and leaves. - A person's newest join or leave per community decides (of equal timestamps, the greater message ID). Joins signed with a delegated key count for the account. A community's owner is always a member.
Cache
- New
membershipstable: per signing key and community, the newest join or leave. Members are resolved through the delegations when they're read. A join or leave goes into it before the trie and is taken out again if the trie write fails; at startup the primary puts every join and leave of the trie into it again (one walk of the trie for communities, joins and leaves). store.leavesandstore.poststake a list of types.
API
POST /api/v1/listtakestype: "join"andtype: "leave".- Community objects have
members(the owner included).GET /api/v1/communities?member=<address>lists the communities someone joined or owns; it isn't cached. - When the node's API takes a join, the community's owner gets a push
notification ("New member",
<name> joined <community>) and a Farcaster mini app notification, with the rules of upvote notifications. Leaves, the owner's own joins and joining again within a day don't notify. GET /api/v1/activityhasjoins: who joined your communities. The notifications page shows them grouped per community.
Migration
All nodes must upgrade together. A 14.0.0 node will not sync with a 13.0.0 node. The website and the apps still keep joined communities locally until they sign joins and leaves.
Communities: read API and feeds (no protocol change)
GET /api/v1/communities(q,owner,limit) lists communities, most active first, andGET /api/v1/communities/:owner/:keyreturns one, or 404.- A community's feeds are namespaced by it:
/api/v1/communities/:owner/:key/feeds/hot|new|bestand the rows endpoints under the same path. They're Kiwi News' Hot, New and Best with only the community's stories, ranked with the same rules. An invalid ID is a 400. - Kiwi News' feeds (also
/,/newand/beston the website) are one feed: they have every story, a community's ones included, ranked as always. - Stories in the feeds and from
GET /api/v1/storieshavecommunity:nullor{id, name, icon, color}. The cache hands the column on ascommunityId, so the bare ID no longer goes out ascommunityanywhere. /c/*in the universal links.
Communities: web UI (no protocol change)
- A community's pages:
/c/<owner>/<key>(Hot),/c/<owner>/<key>/newand/c/<owner>/<key>/best, with its header card and house rules; with?via=<address>, the page an invite link opens. /communitieslists them (Discover, most active first, with search, and Joined) and/communities/newstarts one, or edits yours, with a live preview, icon suggestions from its name and a picker of Phosphor's icons.- Joining keeps a community in the browser (it isn't on the protocol): the feeds show the communities you joined as tabs and in the side box.
- Stories show their community's tag before the domain.
- Submit: "Post to" picks Kiwi News or one of your communities; a post to a
community is signed as the 5-field
Message. - "Communities" in the navigation.
Fixes (communities, no protocol change)
- A community's owner is resolved through the delegations when a post is judged or the community is read, not when the node stored it. Before, a node that stored a community before it knew the signer's delegation kept it under the delegated key, and nodes could judge posts to it differently forever. The communities table is now keyed by signer and name; the old table is dropped on start and refilled from the trie.
- A node loads its delegations before it takes messages over p2p or the API.
- A community goes into the table before the trie, and is taken out again if the trie write fails, so a failed cache write can't leave a node rejecting its posts.
- Refilling the communities table at startup runs in batches and yields, and skips broken entries.
0.16.0
BREAKING CHANGE: User communities
Anyone can now run a community and post to it. A community belongs to the
identity that signs it and lives in its namespace (<owner address>/<name key>), so creating one needs no consensus. See "Communities" in the protocol
docs.
Breaking Changes
- Protocol versions bumped:
- Pubsub topics (roots/messages):
12.0.0 → 13.0.0 - Protocols (leaves/levels):
15.0.0 → 16.0.0
- Pubsub topics (roots/messages):
- New message type
community, signed asCommunity(string type,string name,string about,string[] rules,string icon,string color,uint256 timestamp): a name (24 characters), a one-line description (100), up to 5 house rules (140 each), a Phosphor Icons 2.1.1 icon and a hex colour (#RRGGBB). Signing one again under the same name updates it. - Amplify gets an optional
community(<owner address>/<name key>). With it, the message is signed as the 5-fieldMessage(string title,string href,string type,uint256 timestamp,string community). Nodes accept it only if the community exists in an older version. Upvotes and comments are unchanged.
Cache
- New
communitiestable (newest version of each community, and when it was first created) and acommunitycolumn onsubmissions, added to existing caches on start. POST /api/v1/listtakestype: "community".
Migration
All nodes must upgrade together. A 13.0.0 node will not sync with a 12.0.0 node. Feeds don't treat community posts differently yet; that comes with the clients.
0.15.0
BREAKING CHANGE: Chunk trie sync so one frame no longer grows with the tree
Sync used to put every node of a level, and then every missing leaf, into a single length-prefixed message. That payload grew with the trie, which is why maxDataLength went 4MB → 8MB → 16MB → 32MB.
A level and its missing leaves are now sent as several frames of at most 1 MiB. Each frame is the same CBOR array as before, so the receiver stores it the same way. maxDataLength is 2 MiB: a ceiling for one frame, not for the trie. It does not need to move again as the tree grows.
Breaking Changes
- Protocol versions bumped:
- Pubsub topics (roots/messages):
11.0.0 → 12.0.0 - Protocols (leaves/levels):
14.0.0 → 15.0.0
- Pubsub topics (roots/messages):
Migration
All nodes must upgrade together. A 15.0.0 node will not sync with a 14.0.0 node.
0.14.0
BREAKING CHANGE: Add text post support with inline content
This release adds support for submitting text-only posts using data:text/plain, URLs, allowing users to share thoughts and discussions directly without requiring external links.
Breaking Changes
- Protocol versions bumped:
- Pubsub topics (roots/messages):
10.0.0 → 11.0.0 - Protocols (leaves/levels):
13.0.0 → 14.0.0
- Pubsub topics (roots/messages):
- Validation schema updated:
hreffield now acceptsdata:text/plain,URLs (up to 2048 chars)- Pattern:
^(https?://|data:text/plain,|kiwi:0x)
New Features
- Text Post Submission: New textarea in submit form with 2048 character limit
- Inline Storage: Text content stored as
data:text/plain,URLs withencodeURIComponentencoding - Story Display: Text content renders on story pages between subtitle and action buttons
- Feed Behavior: Text posts appear in feeds without preview (consistent with comment links)
- No Domain Badge: Text posts don't show domain badges (data: and kiwi: URLs)
- Preview Generation: OG images generated with empty domain for social sharing
Security
- DOMPurify sanitization at all rendering points
- Frontend validation: maxLength constraint (2048 characters)
- Safe encoding: encodeURIComponent for storage
- Title sanitization: getSlug() uses DOMPurify
Bug Fixes
- Fixed frame image path generation (was incorrectly stripping extra characters from index)
Migration
All nodes must upgrade to maintain P2P connectivity. Nodes on different protocol versions will not sync with each other. Text posts will only be recognized by nodes running 0.14.0+.
0.13.0
BREAKING CHANGE: Increase P2P sync message size limit to 32MB
- Increased
maxDataLengthfrom 16MB to 32MB in sync protocol (src/sync.mjs) - Fixes "message length too long" errors during trie reconciliation
- Required as trie grows larger and deeper levels contain more nodes to sync
- Protocol versions bumped:
- Pubsub topics (roots/messages):
9.0.0 → 10.0.0 - Protocols (leaves/levels):
12.0.0 → 13.0.0
- Pubsub topics (roots/messages):
Migration: All nodes must upgrade to maintain P2P connectivity. Nodes on different protocol versions will not sync with each other.
0.12.0
BREAKING CHANGE: Complete removal of Kiwi Pass NFT-based access control system
This release removes the entire NFT/allowlist concept from Kiwistand and simplifies to delegation-only access control.
Breaking Changes
- Protocol versions bumped:
- Pubsub topics:
8.0.0 → 9.0.0 - Protocols (leaves/levels):
11.0.0 → 12.0.0
- Pubsub topics:
- API Changes:
- Removed
/allowlistendpoint - Removed
fetchAllowList()from frontend API
- Removed
- Delegator2 SDK (
@attestate/delegator2):eligible(allowlist, delegations, address)→resolveIdentity(delegations, address)- Removed all NFT-related functions:
eligibleAt(),extractLegacyObject(),legacyEligibleAt(),_eligibleAt()
Removed Components
Backend:
src/chainstate/mainnet-mints.mjs- Mainnet NFT trackingsrc/chainstate/mint.config.crawler.mjs- NFT mint crawlersrc/chainstate/transfer-loader.mjs- NFT transfer loader- Registry functions:
allowlist(),refreshAccounts(),augmentWithMainnet()
Frontend:
src/web/src/BuyButton.jsx- NFT purchase componentsrc/web/src/TelegramLink.jsx- Telegram integrationsrc/web/src/LeaderboardStats.jsx- Community statssrc/views/kiwipass-mint.mjs- NFT minting pagesrc/views/indexing.mjs- NFT indexing wait pagesrc/views/invite.mjs- Invite pagesrc/views/leaderboard.mjs- Community leaderboard- Routes:
/community,/invite,/indexing,/kiwipass-mint
How It Works Now
- Any address can act on its own behalf
- If an address has delegated to another address,
resolveIdentity()resolves it to the delegator - No more NFT/allowlist checks anywhere in the codebase
- All identity resolution is done purely through the delegation system
Migration Guide
If you're running a node:
- Update to the latest version
- The system will automatically work with the new delegation-only model
- No manual migration needed - existing delegations continue to work
If you're using the API:
- Replace calls to
/api/v1/allowlistwith/api/v1/delegations - Update any code using
eligible()to useresolveIdentity()
0.11.0
(breaking) Fixed critical bug in allowlist crawler that halted allowlist progression since the Delegator3/smart account switch in August 2025.
Background: When commit 14baab66 updated the system to use Delegator3
contract (0x418910fef46896eb0bfe38f656e2f7df3eca7198), the ABI for the setup
function was correctly updated to match Delegator3's on-chain interface, which
only accepts bytes32[3] data as a parameter (removing the beneficiaries and
amounts arrays that existed in Delegator2).
However, the transfer-loader.mjs code was not updated accordingly - it still
attempted to extract and use input.beneficiaries and input.amounts after
decoding. Since these fields no longer exist in Delegator3's return value, this
caused the decoder to throw errors on every Delegator3 transaction, completely
halting the allowlist crawler.
Impact: The allowlist stopped updating after the Delegator3 switch. Users who minted Kiwi Passes during this period (several weeks) were not added to the allowlist and could not interact with the site. All production nodes were affected.
Fix (commit c8a92a7c):
- Removed the code that tried to access non-existent
beneficiariesandamountsfields (src/chainstate/transfer-loader.mjs:67-76, 113-122) - Added try-catch blocks around
decodeFunctionDatato handle any decoding errors gracefully - Now correctly processes Delegator3 transactions with the updated ABI
How to upgrade your existing node?
CRITICAL: All nodes running the broken version have corrupted/incomplete
op-call-block-logs-load data and MUST re-sync:
- Navigate to your
DATA_DIR - Delete the
op-call-block-logs-loaddirectory - Run:
DATA_DIR=/path/to/your/data npm run sync - Wait until the logs show: "op-call-block-logs loader strategy has finished"
- Your node is now fully synchronized with the correct allowlist
0.10.1
- Fix
npm run reconcile
0.10.0
(breaking) To make Kiwi News compatible with smart wallets and EIP-4337, we've made some breaking changes to the delegator2 contract.
Instead of relying on transaction.from, it now emits msg.sender
from within the contract, hence allowing paymasters etc. to call etch in the
name of the Kiwi Pass controlling address.
This however means that we have, for the time being, added all other signers as a plain text file to the code base, meaning that the old delegator2 contract at 0x08b7ECFac2c5754ABafb789c84F8fa37c9f088B0 will not count anymore when delegating upvoting/commenting power to a new address. Instead all apps will have to start using the new contract: 0x418910fef46896eb0bfe38f656e2f7df3eca7198. For a proper upgrade path where signers can both be revoked and used from both delegator2 contracts we'll have to combine their logs. It's not clear to me whether all of this work would be worth it or whether it would be simpler to just call the delegator2@0.5.1 period its own epoch, add all its messages to a merkle tree and then just consider them valid if they appear as a member. For this release, I did the simplest possible thing because I felt as if this work wouldn't pay off to do properly as we're still not really making money with Kiwi.
Anyways, as the above change will interact with what messages nodes are considering valid, we've bumped the version to 0.10.0 and we've also upgraded our version identifiers in libp2p. We highly recommend all node operators to upgrade.
How to upgrade your existing node?
- Run
npm ithen runcd src/web && npm i -f - In your
DATA_DIR, renamelist-delegations-load-2(to back it up) - Run
npm run sync. It will run very briefly (this is fine) - That's it, you should be good to go
0.9.0
- (breaking) Add token tracking to allow to precisely determine during reconciliation if someone was eligible to post during a period. For more details of what this feature consists of, read the changelogs of the dependencies:
Essentially with this release we're laying the groundwork for our later 1 pass = 1 upvote change. For more details, see this document.
If you're running a node in version 0.9.0 it is required to re-sync the node
using npm run sync. For this, in your DATA_DIR, delete the
op-call-block-logs folder, then run npm run sync until you catch up again
with the OP mainnet chain tip. This is necessary as we have to load each chain
interactions respective tokenId into the mints database.
-
(breaking) For those who are running a kiwistand node in
npm run reconcilemode, we've fixed a bug that could have allowed an upvote to be stored twice. If you've done this, please delete thedata.mdbandlock.mdbfile from yourDATA_DIRand re-reconcile with the network from scratch. -
We're including many other changes in this release, none of which are supposed to be breaking. Most changes are related to the product and the front end.
This release doesn't include any breaking changes to the protocol itself, which is why we've also not bumped the protocol version identifiers. It, however, contains the above breaking changes for anyone currently running a node, which is why this release is a minor patch version increase.
0.8.0
- (breaking) Double it-length-prefixed length
- Fixes in reconciliation algorithm
Caution: If you're running a node and you must preserve its reconciliation data, it is best get in touch with us directly via Telegram. If you can afford to just delete the reconciliation data and start over, we suggest you do this.
0.7.0
- (breaking) Fixed a bug in the constraints metadata db. Details: https://github.com/attestate/kiwistand/commit/37ba938a811b39ec6e88887eaa278d393b72ff6b Caution: If you're running a node, read this carefully and potentially get in touch!
- Hardcode mainnet mints to preserve mainnet timestamps of minting
- Optimism mint crawler records timestamps of mints
- Mints can now be tracked separately
- Many frontend updates
0.6.0
- Upgrade all libp2p version identifiers
- Move all eligible-defining NFTs to
oeth:0x66747bdc903d17c586fa09ee5d6b54cc85bbea45 - Deprecate
RPC_HTTP_HOST - Fix bugs with indexing logs. attestate/crawler
orderfunction was ordering logs based on blockIndex and transactionIndex, but logIndex was necessary too.- We've essentially created two new indexing strategies ("list-delegations-2", and "op-call-block-logs").
- store.posts now tolerates finding "in-eligible" messages
- Added docs that explain how Kiwi News Protocol's set reconciliation works
- Use a DFS to traverse the tree for posts (thank you to @freeatnet!)
0.5.0
- Deactivate revocations temporarily until we're properly implementing them in the set reconciliation algorithm. Details.
- Upgrade all protocol major versions.
0.4.0
- Require new environment variable
OPTIMISM_RPC_HTTP_HOST - Website: Minor stylistic changes
- Protocol:
- All semantic version identifiers have been upgraded as a major version to avoid name collisions with nodes running older versions of this software.
- API
- POST /api/v1/list endpoint now adds two new properties to a message
identityandsigner. - POST /api/v1/messages now accepts messages signed from a delegate address.
- POST /api/v1/list endpoint now adds two new properties to a message
- Docs
- Document difference in ports between API and frontend
0.3.0
- Environment Variables
- Rename
TIMESTAMP_TOLERANCE_SECStoMAX_TIMESTAMP_DELTA_SECS - Separate
HTTP_PORT(for website) andAPI_PORT(for node API) - Remove all
TODAYS_EDITOR_...variables
- Rename
- Add Sphinx docs
- Many changes to the frontend (won't list details)
- Separated website frontend from node API frontend. They now run on differnet ports.
- The allowlist registry now guarantees returning a set of unique Ethereum addresses.
- The level's remote comparisons are now validated at the receiving node with a JSON schema.
- For the frontend (mainly), ecrecover can now cache recovering signatures.
- Logging of errors in the reconciliation algorithm has been improved.
- The "leaves" function can now be called with a "startDatetime" parameter to speed up database queries.
- Database migration
- This release contains an incompatible database migration from earlier releases! If you actually need to migrate from 0.2.0 to 0.3.0, contact @timdaub!
- LMDB now uses "ordered-binary" key encoding to enable range based key queries.
- Our message hashing and identity generation was broken and so messages weren't canonically hashed. This had to be addressed in a database migration and by fixing the canonicalization. Again, if you really must migrate your data, make sure to contact us beforehand!
- We enabled "useNodePruning" in ethereumjs/trie (this gets rid of deleted nodes).
- Protocols
- Upgrade "leaves" protocol to v3.0.0
- Improve PMT traversal algorithm that had a bug when branching factor was too big (vendored in WalkController from ethereumjs/trie).
- Improved atomic committments of data entry within set reconciliation. Made both metadb and trie transactional.
0.2.0
- We discovered a critical issue in the v0.1.0 code base that lead to digests of messages in the ordering algorithm to be non-canonical [1] (thanks @freeatnet).
- We're fixing this using npm:canonicalize that implements RFC8785 for canonical JSON.
- Since we already had nodes in the network with existing data we shipped our
first migration too. It checks for the digest algorithm, and applies a
migration accordingly. We strongly recommend doing a backup of the
DATA_DIRbefore applying the v0.2.0 update. - To avoid synchronizations between v0.1.0 nodes and v0.2.0 nodes, we upgraded all protocol and pubsub identifiers and versions too to avoid corrupting data bases.
1: https://github.com/attestate/kiwistand/commit/debb66ab676053a82b9aab789f68049a5c9a4528
0.1.0
- Initial release of Kiwi News run until 2023-05-06