The Diamond Architecture: How THRYX Upgrades Without Downtime
6 min read
ThryxProtocol is deployed as a single Diamond proxy at 0x2F77b40c124645d25782CfBdfB1f54C1d76f2cCe on Base mainnet. This address will never change — even as the protocol ships major upgrades. Here's how the EIP-2535 Diamond pattern makes that possible.
What Is a Diamond?
A Diamond is a proxy contract that delegates function calls to different implementation contracts called "facets." When you call buy() on the Diamond, it looks up which facet handles that function (SwapFacet) and delegates the call there. Storage lives in the Diamond itself, not in the facets — so swapping a facet doesn't lose any data.
Think of it like a building with interchangeable departments. The address (building) stays the same. The departments (facets) can be renovated or replaced without moving everyone to a new building.
ThryxProtocol's 7 Facets
| Facet | Responsibility |
|---|---|
| DiamondCutFacet | Add, replace, or remove facets (upgrade mechanism) |
| DiamondLoupeFacet | Introspection — list all facets, functions, and interfaces |
| LaunchFacet | Token deployment, metaLaunch (gasless), launchAndBuy |
| SwapFacet | buy(), sell(), swap(), estimateSwap(), V4 THRYX routing |
| PaymasterFacet | Gas sponsorship reserve, self-funding from 20% of swap fees |
| AdminFacet | Fee configuration, protected tokens, owner-only controls |
| ViewFacet | getCurveInfo(), getProtocolStats(), getDeployedTokens(), agentQuickstart() |
How Upgrades Work
When a new version of SwapFacet is deployed, the owner calls diamondCut() on the DiamondCutFacet. This function takes an array of FacetCut structs specifying which functions to add, replace, or remove. The Diamond updates its internal function selector mapping, and the next time someone calls buy(), it routes to the new SwapFacet code.
Users never need to migrate. All token data, balances, curve states, and fee accumulators remain in the Diamond's storage. The address stays the same. Approvals remain valid. Nothing breaks.
The 24KB Bytecode Limit
Solidity contracts have a 24KB maximum bytecode size (EIP-170). Complex protocols easily exceed this. The Diamond pattern solves it by splitting logic across multiple facets, each well under 24KB. ThryxProtocol's SwapFacet, which handles buy/sell, approve patterns, V4 THRYX routing, and slippage protection, was carefully optimized to fit within this limit.
Post-Deploy Verification
After every upgrade, a verification script runs automatically. It calls every view function on the Diamond to confirm all parameters are intact: fee rates, graduation thresholds, protected token lists, paymaster balances, and deployed token states. This catches any storage layout issues before they can affect users.
Version History
The Diamond has been through 7 major versions in its lifetime:
- v1.0 (Mar 10) — Initial launch with Doppler + dual LP (Aerodrome V2 + Uniswap V4)
- v2.1 (Mar 11) — Virtual bonding curves with THRYX as quote token, unified swap()
- v2.2 (Mar 12) — Graduation treasury, referrals, fee burns, loyalty rebates
- v2.3 (Mar 13) — Auto-distribute creator fees on every swap, 70/30 split
- v2.4 (Mar 14) — EIP-2535 Diamond proxy, gasless metaLaunch, PaymasterFacet
- v2.4.2 (Mar 15) — Dynamic graduation, V4 THRYX routing, bot discovery events
- v2.4.3 (Mar 16) — Accessibility fixes, webhooks, analytics
All 7 versions — same address, zero downtime, no user migration. That's the Diamond pattern in practice.
Inspecting the Diamond
The DiamondLoupeFacet implements ERC-165 interface detection and lets anyone enumerate all facets and their function selectors. Call facets() to get the full list, or facetFunctionSelectors(facetAddress) to see which functions a specific facet handles. This makes the Diamond fully transparent and auditable on-chain.
// Inspect the Diamond on-chain
const diamond = new ethers.Contract(
"0x2F77b40c124645d25782CfBdfB1f54C1d76f2cCe",
[
"function facets() view returns (tuple(address,bytes4[])[])",
"function facetAddresses() view returns (address[])",
],
provider
);
const allFacets = await diamond.facets();
console.log(`${allFacets.length} facets deployed`);