Skip to content

Conversation

@salzbrenner
Copy link
Member

@salzbrenner salzbrenner commented Oct 20, 2025

Description

This PR introduces a new reads/ module that extracts and reorganizes all read-only blockchain operations from SpaceDapp into a composable foundation. This is Phase 1 of a multi-phase effort to both migrate to viem and separate read and write operations, ultimately replacing SpaceDapp.

Migration Path

This PR completes Phase 1 of a planned migration:

  1. ✅ Phase 1 (this PR): Extract read primitives into reads/ module
  2. Phase 2 (future): Add more domains (spaces, channels, etc.), expand coverage
  3. Phase 3 (future): Deprecate SpaceDapp read methods, guide migration to createReadApp
  4. Phase 4 (future): Remove SpaceDapp read methods entirely (write-only)

Benefits

  • Separation of Concerns: Clear boundary between read and write operations
  • Composability: Use primitives directly or through domain-organized API
  • Type Safety: viem provides superior type inference for contract reads
  • Testability: Easier to mock and test read operations independently
  • Performance: Centralized cache management, potential for future optimizations
  • Developer Experience: Clean, documented API with usage examples in reads/README.md

Changes

No functional changes to existing behavior

  1. New reads/ Module Architecture
    Created 18 new files organized into a layered, composable architecture:
  • clients/ - viem-based PublicClient creation and chain configuration
  • cache/ - Centralized CacheManager with specialized caches (entitlements, banned tokens, token ownership)
  • contracts/ - Contract read wrappers (Space, SpaceFactory, DelegateRegistry)
  • aggregators/ - Higher-level operations composing contracts and caches (banned wallets, linked wallets)
  • app/ - Domain-organized API via createReadApp() that bundles primitives into a cohesive interface
  1. SpaceDapp Refactoring

Breaking Change: SpaceDapp constructor now requires a ReadApp parameter:

  // Before
  const spaceDapp = new SpaceDapp(config, provider)

  // After
  const readApp = createReadApp({ chainId, url, spaceFactoryAddress })
  const spaceDapp = new SpaceDapp(config, provider, readApp)

Removed Properties:

  • pricingModules → Use readApp.pricingModules instead
  • platformRequirements → Use readApp.platformRequirements instead
  • 5 cache properties → Now managed by readApp.cacheManager

Result: Significantly simplified SpaceDapp (~467+ lines reduced), net -704 lines across the repository.

  1. Ethers → Viem Migration for Reads
  • All migrated read operations now use viem's PublicClient for better type safety and performance
  1. Deprecated & Refactored Components

Deleted files (logic moved to reads/):

  • cache/EntitlementCache.ts → reads/cache/entitlements.cache.ts
  • delegate-registry/DelegateRegistry.ts → reads/contracts/delegate-registry.ts
  • platform-requirements/PlatformRequirements.ts → Integrated into reads
  • pricing-modules/PricingModules.ts → Integrated into reads

WalletLink refactored:

  • WalletLink class still required because it has write methods
  • Now accepts walletReads in constructor
  • Deprecated getLinkedWallets(), getRootKeyForWallet(), checkIfLinked() (use readApp.wallets instead)
  1. Updated All Consumers
  • @towns-protocol/bot - Updated bot instantiation
  • @towns-protocol/sdk - Updated all test utilities and sync agent
  • @towns-protocol/stream-metadata - Updated contract utilities
  • @towns-protocol/playground - Updated space app hooks

Checklist

  • Tests added where required
    - All existing tests updated and passing
    - Tests verify proper integration of readApp with SpaceDapp
  • Documentation updated where applicable
  • Changes adhere to the repository's contribution guidelines

@vercel
Copy link

vercel bot commented Oct 20, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
river-sample-app Canceled Canceled Oct 20, 2025 10:37pm

💡 Enable Vercel Agent with $100 free credit for automated AI reviews

@vercel
Copy link

vercel bot commented Oct 20, 2025

Deployment failed with the following error:

Failed to create deployment for team_3Iu5Hm7LoF9mvJKVdGw5cM7O in project prj_TLqglKpXVIXAvCSeg0t2IcneYrM1: FetchError: request to https://76.76.21.112/v13/now/deployments?ownerId=team_3Iu5Hm7LoF9mvJKVdGw5cM7O&projectId=prj_TLqglKpXVIXAvCSeg0t2IcneYrM1&skipAutoDetectionConfirmation=1&teamId=team_3Iu5Hm7LoF9mvJKVdGw5cM7O&traceCarrier=%7B%22ot-baggage-webhookAt%22%3A%221760993736902%22%2C%22ot-baggage-senderUsername%22%3A%22gh.salzbrenner%22%2C%22baggage%22%3A%22webhookAt%3D1760993736902%2CsenderUsername%3Dgh.salzbrenner%22%2C%22x-datadog-trace-id%22%3A%224659331558607531519%22%2C%22x-datadog-parent-id%22%3A%228700851989754815101%22%2C%22x-datadog-sampling-priority%22%3A%222%22%2C%22x-datadog-tags%22%3A%22_dd.p.tid%3D68f6a1c800000000%2C_dd.p.dm%3D-3%22%2C%22traceparent%22%3A%2200-68f6a1c80000000040a9455dc991adff-78bfa2c91c96227d-01%22%2C%22tracestate%22%3A%22dd%3Dt.tid%3A68f6a1c800000000%3Bt.dm%3A-3%3Bs%3A2%3Bp%3A78bfa2c91c96227d%22%7D failed, reason: socket hang up

@salzbrenner salzbrenner changed the title Evan/web3.reads refactor(web3): Extract read operations into composable reads module Oct 20, 2025
@salzbrenner salzbrenner requested a review from shuhuiluo October 20, 2025 22:12
@salzbrenner salzbrenner marked this pull request as ready for review October 20, 2025 22:13
}): SpaceFactoryReads {
const { spaceFactoryAddress, publicClient } = args
const makeInstance = <Abi_ extends Abi>(abi: Abi_) =>
getContract({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried using getContract in bot framework to expose bot app contract functions, however I got stuck.

I couldn't make it compose .

const buildBot = <abi extends Abi>(customAbi: abi) => {
   const app = getContract({
     address: appAddress,
     abi: [...simpleAppAbi, ...customAbi]
     client,
    })
  // app.read <-- all the types was gone
 }

After looking for what should I do in wevm community, I found this reply from jxom:

jxom: Honestly, not using getContract is better for complex projects.
jxom: Just pass a central stateless contract config around to Viems contract actions (readContract, writeContract, etc).

Not sure if that's relevant feedback, but would like to share my experience (could be skill issue tbh)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jxom is the goat behind viem.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need as const ABI for type inference.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting...he also said they only added getContract b/c people are used to ethers OO. Ha, that is what I'm used to

but yeah like Shuhui said you probably just need type inference on the abi to get it working

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had as const
Probably bc the Abi wasn't known at compile time.. but I was expecting at least the SimpleApp functions to show

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But you spreaded them.

Copy link
Contributor

@miguel-nascimento miguel-nascimento left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sharing some thoughts!

} => {
const cacheManager = new CacheManager()

const readClient = createReadClient({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: let user pass viem client to createReadApp instead.
it will grant more options / reusability

@salzbrenner salzbrenner marked this pull request as draft October 21, 2025 14:33

export type ReadApp = ReturnType<typeof createReadApp>

export const createReadApp = (args: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. this is a pretty generic name for a pretty specific app... we already have 2 dapps (or is it 3 now with the app registry?) and will probably get more - so can we make a pattern & naming convention here so that we can do the same thing everywhere?
  2. it's unclear to me why we need to pass a read app to the space dapp, can the space dapp just make a read app in it's constructor? what are the pros/cons there?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants