RFC: Upgradeable Beacon Proxies for Shared Interfaces

I’ve been looking at whether a beacon proxy pattern makes sense on Starknet. My current view is yes, but only as a narrow, Starknet-specific standard for contracts that share a known interface, with some potential extensions, each with trade-offs.

Why this is worth discussing

Starknet already solves single-contract upgrades well with replace_class_syscall.

But there is still an open problem: how do we coordinate upgrades across many deployed contracts that should keep the same interface and switch to the same implementation together?

This shows up in patterns like:

  • pools
  • vaults
  • markets
  • per-user accounts
  • other fleets of homogeneous contracts

Today the options are:

  • upgrade each contract one by one (or redeploy)
  • build app-specific orchestration

A standardized orchestration pattern would improve ecosystem-wide discovery and tooling by replacing bespoke app-level coordination with a common surface.

Starknet constraints

The idea should not be framed as “bringing EVM beacon proxies to Starknet” for a few reasons:

  • library_call gives us the delegatecall-like primitive we need, but
  • there is no fallback entrypoint
  • so a generic catch-all proxy is not possible

That means any realistic beacon proxy pattern on Starknet has to either be interface-oriented

  • the proxy explicitly exposes the functions it forwards
  • the shared interface is known ahead of time
  • the beacon coordinates the implementation class hash used by many proxies

Or implement a custom mechanism for routing custom dynamic entrypoints, like the later proposed beacon_call function, but this hurts composability because on-chain contracts can’t interact with those routers as they would do with regular contracts.

Proposed scope for a first standard

I think a first SNIP should stay narrow and standardize:

  • IUpgradeableBeacon: returns the current implementation ClassHash and exposes upgrade_implementation_hash(new_implementation_hash)
  • IBeaconProxy: exposes the beacon address and current implementation hash
  • forwarding rules that require library_call for the typed proxy path
  • SRC-5 discoverability for beacons and beacon proxies

Note: The reason to use upgrade_implementation_hash and not just upgrade is to avoid confusion between replacing the beacon’s own class hash via replace_class_syscall and replacing the implementation hash in storage.

Optional beacon_call extension

One optional extension that may be worth standardizing is:

  • beacon_call(selector, calldata)

The intention is to let the proxy act as a beacon-backed router for arbitrary entrypoints. In other words, instead of only forwarding a fixed set of functions compiled into the proxy, the proxy can also accept a selector plus calldata, resolve the current implementation from the beacon, and route that call through library_call.

This is useful for teams that expect the implementation surface to evolve over time:

  • new entrypoints can be reached without redeploying a new proxy class
  • one proxy class can remain valid while the implementation ABI grows
  • frontend or SDK-driven integrations can manually encode calls when needed

The main limitation is composability. Unlike Solidity proxies, Starknet does not have a fallback entrypoint that automatically catches unknown selectors. That means beacon_call does not make the proxy behave like it directly exposes every function on the implementation.

In practice:

  • typed dispatchers will not call those routed functions by default
  • regular on-chain contract calls to undeclared entrypoints on the proxy will fail
  • other contracts cannot treat the proxy as if it natively implements the full evolving interface

So beacon_call is best understood as an explicit router extension, not as a substitute for a typed proxy surface. It can reduce proxy churn, but it does not provide the same default on-chain composability that fallback-based proxies provide in Solidity.

Why this improves DX

A standard beacon pattern would give:

  • one implementation-hash update that switches many proxies
  • stable instance addresses
  • shared auditable implementation logic
  • a common interface for explorers, indexers, SDKs, and auditors
  • less bespoke proxy plumbing across projects

Main risks and tradeoffs

The main downsides are:

  • one bad implementation-hash update affects the entire fleet
  • storage compatibility still has to be maintained across versions
  • without fallback, new typed functions still require proxy changes
  • if beacon_call is included, it widens the callable surface without proper (automatic) interface handling

beacon_call should not redefine SRC-5 semantics. If a proxy exposes a generic routing entrypoint, that does not mean it directly implements every routed business interface.

Questions for feedback

I’d especially like feedback on:

  • Is there enough ecosystem demand for a shared beacon/proxy standard?
  • Is beacon_call useful as it is presented? Should it be part of v1, or deferred to a later extension?
  • Should IBeaconProxy require both beacon() and implementation_hash(), or is beacon() enough?
  • Are there any missing safety requirements around beacon validation or implementation-hash updates?

If the direction seems useful, I already have a narrow draft in mind and would refine it around the feedback.