Building on Polkadot Hub: A Builder’s Journal

We introduced plaza.fun on the Forum a few weeks ago. This series goes deeper — into the code.

plaza.fun is a native asset launchpad on Polkadot Hub. EVM contracts operating on pallet_assets. Bonding curves, automatic graduation, 90% LP burn, permanent creator earnings via FeeKeyNFT.

This 7-part series documents how we built it — the architecture decisions, the bugs we found at the EVM/Substrate boundary, and what we learned shipping a cross-layer dApp on Hub.

The Series

# Title Date
0 The Plaza Brief — 5-minute overview of the whole system Mar 25 :white_check_mark: Read →
1 Why We Built on Polkadot Hub — the two-layer architecture and what it unlocks Mar 26 :white_check_mark: Read →
2 The Bonding Curve — token creation, pricing formula, safety mechanisms Mar 27
3 The Fee Engine — where the 1% goes, creator earnings across two lifecycle stages Mar 28
4 The Token Lifecycle — graduation, 90% LP burn, FeeKeyNFT, PlazaSwap Mar 29
5 Security & Governance — attack surface map, real bugs, TimelockController + Guardian Mar 30
6 Lessons & What’s Next — what we’d tell the next team building on Hub Mar 31

One post per day, all week. All posts go live at plaza.fun/blog.

Try It

The testnet is live right now. Everything described in this series is running and every contract is verified on Blockscout.

:backhand_index_pointing_right: plaza.fun — connect a wallet, grab testnet DOT from the faucet, create a token, trade on the curve.

Why We’re Writing This

Two reasons:

  1. There’s almost no documentation on building cross-layer dApps on Hub. We hit issues at the pallet_assets ↔ EVM boundary that aren’t covered anywhere. If another team is building here, this might save them weeks.

  2. We think transparency builds trust. The contracts are verified. The architecture is public. This series explains the why behind the what.

We’d genuinely appreciate feedback — especially from anyone who’s worked with pallet-revive or pallet_assets precompiles. Part 5 (Security) covers some edge cases we’re still thinking through.


@plaza_fun · Discord

4 Likes
Part 1 is live: Why We Built on Polkadot Hub

This one covers the architecture — the two-layer design that makes plaza.fun possible.

The short version: Hub gives us EVM smart contracts (pallet-revive) + native assets (pallet_assets) + precompiles as the bridge. Business logic in Solidity, assets in the runtime.


┌──────────────────────────────────┐
│     Solidity Smart Contracts     │
└───────────────┬──────────────────┘
                │
      ┌─────────▼──────────┐
      │  ERC20 Precompiles │
      └─────────┬──────────┘
                │
      ┌─────────▼──────────┐
      │   pallet_assets    │
      └────────────────────┘


We also cover the two-wallet challenge — supporting MetaMask (H160) and Polkadot wallets (SS58) on the same UI, with parallel execution paths.

Would love feedback from anyone who's worked with pallet_assets precompiles or built cross-layer dApps on Hub.

https://plaza.fun/blog/building-on-hub-part-1-why-polkadot-hub

Tomorrow: Part 2 — The Bonding Curve
3 Likes

Part 2 is live: The Bonding Curve :chart_increasing:

How plaza.fun prices tokens from zero to DEX graduation — constant product curve, 7 layers of protection, and the async graduation pattern we designed to work around a `pallet_revive` limitation.

:link: [Read Part 2](https://plaza.fun/blog/under-the-hood-the-bonding-curve)

:bug: [#11525 — Complex call chains revert under pallet_revive]( [Bug]: Complex contract call chains revert under pallet_revive but succeed via EVM RPC · Issue #11525 · paritytech/polkadot-sdk · GitHub )

:bug: [#11526 — Contract size limit DX]( [Feature Request]: Better developer experience for contract size limits on pallet_revive · Issue #11526 · paritytech/polkadot-sdk · GitHub )

1 Like

Part 3 is live: The Fee Engine :money_bag:

How plaza.fun charges 1% per trade and splits it: 25% to the token creator, 75% to the platform. After graduation, the ProtocolFeeCollector — an immutable, non-upgradeable contract — routes DEX fees to FeeKeyNFT holders.

Key safety pattern: push-pull fee distribution. If any recipient reverts, the trade still goes through. Fees get queued for manual withdrawal.

:link: Read Part 3


GitHub issue update: Parity engineer @mokita-jmokita-jmokita-jmokita-jmokita-jmokita-jmokita-jmokita-j responded to both #11525 and #11526. We published minimal reproduction repos with on-chain proof:

3 Likes
Part 4 is live: The Token Lifecycle 🔄

This one covers graduation mechanics — what happens when a bonding curve fills, why we built Hub's first DEX, and the DirectAdapter pattern for Uniswap V2 on pallet_revive.

Key takeaway: graduation is designed to be the moment a token becomes *more* trustworthy, not less. 90% LP burned, creator earns through yield not exit.

Technical highlights:
- Atomic graduation: pool creation + 90% LP burn + vault lock + FeeKeyNFT mint — all in one tx
- DirectAdapter bypasses approve() failure in cross-contract precompile calls
- Async graduation pattern works around pallet_revive weight limits (#11525)

Part 4 — The Token Lifecycle
1 Like

Part 5: Security & Governance is live :shield:

Today’s article maps the full attack surface of a cross-layer dApp on Polkadot Hub and walks through our 4-layer defense:

Contract Security — nonReentrant on every state-changing function, Ownable2Step for ownership transfers, UUPS proxy with TimelockController (24h delay on upgrades).

Economic Security — Per-block volume caps (anti-sandwich), max 2% buy per trade (anti-whale), cooldown periods (anti-MEV), and the structural impossibility of rug pulls (90% LP burned, no withdraw function exists).

Infrastructure Security — API key auth + IP whitelist, Relayer atomic batches with retry logic, auto-recovery on failures, Slack alerts.

Governance Roadmap — Guardian multisig (emergency only) → TimelockController (parameter changes) → Full on-chain governance.

We also discuss the 76 findings from our internal security audit and the 2 upstream bug reports we filed with Parity for pallet_revive (#11525, #11526).

Read → Security & Governance

Tomorrow: Part 6 — Lessons & What’s Next (series finale)

2 Likes

Part 6: Lessons & What’s Next — Series Finale :chequered_flag:

The final piece covers five lessons from building DeFi infrastructure on Polkadot Hub:

1. Precompiles are the bridge — and the bridge has gaps. name()/symbol()/decimals() panic on pallet_assets precompiles. approve() fails in cross-contract calls. Both VMs affected. We built workaround patterns (DirectAdapter, database-backed metadata) that any Hub builder can reuse.

2. We tested both VMs. PVM (resolc): ~10x bytecode bloat, ~10x higher deploy/storage costs, complex call chains fail on both EVM RPC and pallet_revive.call. REVM (standard solc): standard sizes, standard costs, complex calls work via EVM RPC. Our mainnet will use REVM. The official docs agree.

3. Design governance from Day 1. TimelockController on all upgradeable contracts. Immutable where possible — our ProtocolFeeCollector has no owner, no upgrade path, no rescue function.

4. The frontend is the real abstraction layer. wagmi + polkadot-api in parallel. One “Buy” button, two execution paths. The hardest code to test.

5. Atomic operations prevent stuck states. Graduation with try/catch. Push-pull fee distribution. 2-hour creation timeout with reclaim.

Also: PlazaSwap roadmap as Hub’s first general-purpose DEX — permissionless pairs, multi-hop routing, permanent liquidity from graduated tokens.

Read → Part 6: Lessons & What’s Next

:books: Full series now complete:

Thanks to everyone who followed along. Next stop: mainnet :rocket:

3 Likes

I do appreciate the write-up. Looking through the activity, I might be interested to see how you were able to expose native pallet_assets to handle ERC-20 calls. This is something I’m directly looking into at the moment.

@Nukeme3 Thanks for reading through!

Quick clarification on how it actually works:

We do use pallet_assets ERC-20 precompiles — transfer() and balanceOf() work fine. The problem is approve() breaks in cross-contract context.

So for DEX graduation, instead of approve() → router.addLiquidity(), we built a DirectAdapter that transfers tokens straight to the pair and calls mint(). No nested approve needed.

Separately, we filed [Bug]: Complex contract call chains revert under pallet_revive but succeed via EVM RPC · Issue #11525 · paritytech/polkadot-sdk · GitHub for complex call chains reverting under pallet_revive.call — different issue but also limits composability.

What are you building? Happy to share code.

Elvis