A Polkadot dApp is a front end plus logic that lives somewhere in the Polkadot network: a smart contract on Polkadot Hub, a contract on a parachain such as Moonbeam or Astar, or a pallet in a runtime you control. The first decision is where that logic runs; the rest of the stack (client library, wallet connection, indexing) follows from it.
Step one: decide where your logic lives
| Option | Language and tooling | Good for | Trade-off |
|---|---|---|---|
| Smart contracts on Polkadot Hub | Solidity via the Revive toolchain and Ethereum-style RPC; ink! (Rust) also targets the same contracts pallet | Apps that want Polkadot's native assets and users without running a chain | Newer environment; verify tooling maturity and limits for your use case |
| EVM parachain (e.g. Moonbeam) | Standard Solidity with Hardhat or Foundry, MetaMask | Porting an existing Ethereum dApp with minimal changes | Users need the parachain's gas token; less native to the Polkadot account model |
| Wasm or multi-VM parachain (e.g. Astar) | ink! or Solidity depending on the chain | Teams already in that parachain's ecosystem | Tied to one parachain's roadmap and liquidity |
| Your own runtime pallet | Rust with FRAME in the Polkadot SDK | Products that need custom fees, governance or performance | You run a chain and buy coretime; covered in the wider Polkadot development guide |
For most new dApps, deploying contracts rather than launching a chain is the sensible default. Only build a pallet when a contract genuinely cannot express what you need or when the economics of your own chain make sense.
The front end: talking to Substrate-based chains
If your logic is an EVM contract, your front end looks like any Ethereum dApp: viem or ethers, and an EVM wallet. If it touches native Polkadot functionality (balances on Polkadot Hub, staking, governance, XCM, or your own pallet), you need a Substrate client.
The long-standing library is @polkadot/api (polkadot.js). Newer projects increasingly use polkadot-api (PAPI), which generates TypeScript types from chain metadata, is designed around a light client, and keeps bundles smaller; Dedot is another modern alternative. Whatever you choose, a few Polkadot specifics shape the code:
- Metadata-driven types. Calls and storage are described by runtime metadata. When a chain upgrades, types can change. Generating descriptors and testing against upcoming runtimes prevents surprise breakage.
- Light clients. smoldot can run in the browser and verify chain data without trusting an RPC provider, at the cost of a short sync on first load.
- Multiple chains. A user's DOT, stablecoins and identity may sit on different system chains. Your app may need connections to several at once.
- Planck units. DOT uses 10 decimals; never hardcode 18.
Connecting wallets
Polkadot browser wallets (Polkadot.js extension, Talisman, SubWallet) inject accounts into the page through a shared extension interface; mobile wallets like Nova support WalletConnect and in-app browsers. Your connect flow should list available extensions, let the user pick accounts, and pass a signer to your client library. Display SS58 addresses with the correct network prefix. If your dApp mixes EVM contracts with native Substrate calls, decide early whether users will have one account type or two, because that decision ripples through the whole UX. For the wallet side of the stack, see Polkadot wallet development.
Cross-chain features with XCM
XCM (Cross-Consensus Messaging) lets your dApp move assets between parachains or trigger actions on another chain. In practice dApps use it for deposits ("bring USDT from Polkadot Hub to our chain"), withdrawals, and occasionally remote execution. Fee estimation on the destination chain and handling failed messages are the hard parts; test with tools that replay XCM on forked networks before shipping.
Indexing and data
Chain storage is not designed for queries such as "all trades by this user". Most Polkadot dApps add an indexer: SubQuery and Subsquid are the established options, both able to index events from Substrate pallets and EVM contracts. Plan for the indexer as a production service with its own monitoring, not an afterthought.
A realistic build sequence
- Pick the execution environment and confirm the token standards, wallets and liquidity you need exist there.
- Write and test contracts (Foundry or Hardhat for Solidity; the ink! tooling for Rust) on a local node or public testnet.
- Build the front end with PAPI or an EVM library, plus wallet connection and network switching.
- Add an indexer and any backend services such as notifications.
- Audit the contracts, then launch with conservative limits.
A focused dApp (a few contracts, one front end, an indexer) is commonly a two-to-four person team for three to five months, plus an external audit. Cross-chain features and a custom pallet each add months. For general dApp architecture beyond Polkadot, see dApp development, and for token sales in the ecosystem, IDO launchpads on Polkadot.
Frequently asked questions
Can I deploy my existing Solidity contracts on Polkadot?
Yes, either on an EVM parachain like Moonbeam with no changes, or on Polkadot Hub through its Ethereum-compatible contract environment. On Polkadot Hub, test carefully: gas accounting and some low-level behaviors can differ from Ethereum.
Is ink! still relevant?
ink! remains the Rust option for contracts on Polkadot, and recent versions target the same contracts infrastructure as Solidity on Polkadot Hub. Choose it if your team prefers Rust; choose Solidity if you want the larger tooling and auditor pool.
Should I use polkadot.js or PAPI?
For new projects PAPI is usually the better starting point thanks to generated types and light-client support. polkadot.js remains widely used and well documented, so it is a reasonable choice when you depend on existing examples.