Smart Contracts
Overview
Polymesh includes rich native functionality for identity, assets, compliance, portfolio management, and settlement. Smart contracts are used to add custom workflow and business logic on top of those native capabilities.
This allows teams to keep core regulated operations on the native layer while implementing bespoke behavior in contracts, such as:
- exchange and routing logic
- custody workflow automation
- permissioned operations with custom approval paths
- upgradeable application-level policy logic
pallet-revive: a dual-VM contract engine (PolkaVM + EVM)
The smart contract pallet on Polymesh is pallet-revive. It is a dual-VM contract execution engine: it runs two different virtual machines, and which one executes a given contract is determined by how that contract was compiled — not by which way you call it.
- PolkaVM (a RISC-V-based VM) executes PolkaVM bytecode. Solidity compiled with the
resolccompiler (instead ofsolc) targets PolkaVM. This path does not provide full EVM compatibility — some EVM semantics and opcodes are unsupported or behave differently, so a contract that depends on exact EVM behaviour should not assume it carries over. - revm (a Rust implementation of the Ethereum Virtual Machine) executes standard EVM bytecode. Solidity compiled with the ordinary
solccompiler produces EVM bytecode that deploys and runs inrevmwith full EVM compatibility. This is the path to use when a contract must behave exactly as it would on Ethereum.
In short: resolc → PolkaVM (native execution, partial EVM compatibility) and solc → EVM bytecode → revm (full EVM compatibility). EVM-bytecode execution is a distinct execution path enabled by the AllowEVMBytecode runtime flag (see Runtime configuration) — not merely an interface layered on top of PolkaVM. Both VMs live behind the same pallet-revive calls, storage, and account model.
When you deploy a contract you upload its compiled code, and the runtime selects the VM from the code itself — PolkaVM if it's RISC-V/PolkaVM bytecode, revm if it's EVM bytecode. This is independent of how you submit the transaction. A Solidity/EVM-bytecode contract can be deployed and called from an ordinary Substrate transaction (e.g. polkadot.js or subxt, via instantiate_with_code / call) just as well as from standard Ethereum tooling through the eth-rpc proxy. The Ethereum JSON-RPC path is a convenience for standard ETH tooling and workflows — it is not a requirement for deploying or using EVM contracts.
Address mapping
Polymesh accounts are 32-byte (AccountId32), not 20-byte Ethereum addresses. AddressMapper = pallet_revive::AccountId32Mapper<Self> bridges the two, and the two directions work differently:
- Native account → Ethereum address: computed by hashing the 32-byte account with Keccak-256 and taking the last 20 bytes. This is a one-way derivation — the original 32-byte account cannot be recovered from the 20-byte hash output alone.
- Ethereum address → native account: if the account has previously called
map_account, the chain looks up the real account via the storedOriginalAccountmapping. If it hasn't, the chain falls back to a deterministic fallback account: the 20-byte address padded with twelve0xEEbytes to make 32 bytes.unmap_accountremoves a stored mapping.
The native-to-Ethereum direction above is a one-way hash, so the chain cannot invert it on its own. Until an account calls map_account, any transfer sent to that account's derived Ethereum-style address is credited to the 0xEE-padded fallback account instead — a distinct 32-byte account from the real one.
This is not automatically lost: the real account holder can call dispatch_as_fallback_account to dispatch a call (e.g. a transfer) as that fallback account and move the funds out, since the runtime re-derives the same fallback account from their signed origin. But this is a manual recovery step that most wallets and tooling won't surface by default, so the safe practice is to call map_account before ever advertising or receiving funds at your derived Ethereum-style address — mapped accounts receive transfers directly, with no recovery step ever needed.
Tools such as Subscan's account-conversion utility can compute both directions for you (Ethereum → SS58 pads with 0xEE; SS58 → Ethereum performs the Keccak-256 derivation) — useful for looking up addresses, but it does not perform or substitute for the on-chain map_account call.
Calls
pallet-revive exposes the standard upstream call set — Polymesh has not added or removed any calls at the pallet level; customization is entirely in runtime configuration (below):
call,instantiate,instantiate_with_code— native (Substrate) calls to invoke or deploy a contract.instantiate_with_codeaccepts either PolkaVM or EVM bytecode; the runtime selects the VM from the uploaded code, so these deploy Solidity/EVM contracts torevmtoo — not only PolkaVM contracts.eth_transact,eth_instantiate_with_code,eth_call,eth_substrate_call— Ethereum-transaction-shaped entry points (RLP-encoded, ETH-signed) that theeth-rpcproxy uses so standard Ethereum tooling can reach the same contracts. They are an alternative submission path, not a different VM.upload_code,remove_code,set_code— manage contract code independently of instances (again, either bytecode kind)map_account,unmap_account— manage the reversibleAccountId32↔ Ethereum address mappingdispatch_as_fallback_account— dispatch a call as a contract's fallback account
Runtime configuration
Values set in impl pallet_revive::Config for Runtime:
| Setting | Value | Notes |
|---|---|---|
ChainId | 1_641_820 (Mainnet), 1_641_819 (Testnet), 1_641_818 (Develop) | The EVM chain ID wallets and tooling (MetaMask, ethers.js) need to target the right network |
NativeToEthRatio | 10^12 | Bridges Polymesh's 6-decimal POLYX to Ethereum's 18-decimal wei convention |
AllowEVMBytecode | true | Enables the revm EVM-bytecode path (solc output) alongside PolkaVM contracts |
GasScale | 100 | Scales EVM gas to Polymesh's weight-based fee model |
Precompiles | () | No custom precompiles configured yet — see Precompiles below |
AddressMapper | AccountId32Mapper | See Address mapping above |
Interacting with native Polymesh functionality
Precompiles
A precompile is a fixed contract address that, instead of running interpreted bytecode, triggers native logic on the runtime side — from a contract's perspective it looks like a normal contract implementing some interface (an ABI, for the EVM case), but calls to it are handled directly by a pallet rather than by executing PolkaVM/EVM code. This is the mechanism pallet-revive uses to expose native chain functionality to contracts: impl pallet_revive::Config for Runtime has a Precompiles associated type (see the runtime configuration table above) that lists which precompiles are wired in for a given network.
As of this writing, Polymesh's Precompiles configuration is empty (()) on Mainnet, Testnet, and Develop. There is currently no supported way for a contract — PolkaVM or EVM — to call into the Asset, Settlement, or Identity pallets. Contracts are limited to their own storage and native POLYX balance flows (receiving, holding, transferring), which don't depend on a precompile.
Precompiles for interacting with native assets (such as an ERC-20-style interface for fungible assets — transfer, approve, transferFrom, balanceOf, allowance) are planned for a future release. This page will be updated once they ship; until then, treat contract-driven native-asset interaction as a planned capability, not an available one.
Allowances (asset::approve / settlement::transfer_funds)
The Asset and Settlement pallets provide an ERC-20-style approve/transferFrom pattern at the chain level: an asset holder calls asset::approve(asset_id, spender, amount) to authorize a spender to move up to amount of the asset on their behalf, without granting the spender a broader identity permission. The spender then calls settlement::transfer_funds naming the owner's account as the source — no separate signature from the owner is required for that transfer, and the allowance is drawn down accordingly. See Allowances and Direct Transfers for the full mechanics. This is a general chain feature usable today by any permitted caller (an off-chain service, another pallet, or — once the precompile above ships — a contract); it does not itself require a contract.
Running EVM JSON-RPC
pallet-revive is a Substrate pallet, not a full Ethereum node — it doesn't speak Ethereum's JSON-RPC (eth_call, eth_sendRawTransaction, eth_getBalance, etc.) directly. A separate proxy process, pallet-revive-eth-rpc (binary name eth-rpc), translates standard Ethereum JSON-RPC into calls against the node. Run it alongside your node to point MetaMask, ethers.js, or other standard Ethereum tooling at Polymesh, using the ChainId for your target network from the table above.
The eth-rpc proxy exists purely to support standard Ethereum tooling and workflows. It is not required to use EVM contracts: the same revm-executed Solidity contracts can be deployed and called directly through ordinary Substrate transactions (instantiate_with_code / call) using e.g. polkadot.js or subxt. Which VM runs a contract depends on the uploaded bytecode, not on whether you go through eth-rpc — see the note above.
Polymesh's pallet-revive is built on the same upstream pallet used across the Polkadot ecosystem, so Polkadot's smart contracts documentation is a useful reference for standard EVM/Solidity tooling and workflows — dev environments (Remix, Hardhat, Foundry), libraries (ethers.js, viem, web3.js), an ERC-20/NFT/Uniswap cookbook, and general EVM-vs-PVM concepts. Treat it as a guide to the surrounding tooling ecosystem, not to Polymesh's own configuration: runtime settings like ChainId, Precompiles, and address mapping are specific to Polymesh's deployment and are documented above.
Error handling
pallet-revive has a large, themed error surface (contract/code lookup, execution & gas, calls & reentrancy, storage & deposits, code/blob validation, delegate dependencies, account mapping & syscalls, and EVM/Ethereum compatibility). For a full list of error definitions refer to the chain's own metadata for the network and runtime version you're targeting (via Subscan runtime explorer, polymesh developer app, polkadot.js apps, subxt, or the Polymesh SDK's generated types). If you're building a wallet, indexer, or dApp framework against pallet-revive, treat the metadata as the source of truth; expect most error conditions to be execution or storage-limit conditions specific to the PolkaVM environment.
Getting started checklist
- Target
pallet-revivefor all contract work on Polymesh. - To interact with contracts using standard Ethereum tooling, run the
eth-rpcproxy alongside your node and use the correctChainIdfor your network. - Have users call
map_accountbefore they receive funds at their derived Ethereum-style address — see the warning in Address mapping. - Contracts cannot yet call into native Polymesh pallets (no precompiles are configured) — see Precompiles. Native-asset precompiles are planned for a future release.