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
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.
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:
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.
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.
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
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.
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.
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:
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
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.
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).
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.
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.
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.