Fungible Asset Precompile Changelog - v8.0 to v8.1.2
This page covers what shipped for fungible assets on the EVM side. It is part of the v8.0 → v8.1.2 changelog.
Audience: smart contract developers, EVM wallet and tooling integrators, and asset issuers who want their asset usable from Solidity.
This is a release note, not the reference. The precompile is documented in full under Smart Contracts → Precompiles:
- Precompiles — the address scheme and the semantics shared by all precompiles
- Fungible Asset (ERC-20) — the method reference, events, revert reasons, and design considerations
Overview
v8.0 added pallet-revive, giving Polymesh a dual-VM (PolkaVM + EVM) contract engine, but contracts had no way to touch native Polymesh assets — the assets tracked by pallet-asset with identity, compliance, and settlement enforcement. v8.1 closed that gap, and v8.1.1 completed the interface.
A crate, pallet-precompiles, provides FungibleAssetInterface, wired into pallet_revive::Config::Precompiles on all three runtimes (Mainnet, Testnet, Develop).
What it gives you:
- Every fungible asset is an ERC-20 token, at a deterministic address derived from its
AssetId(0x<assetId>00080000). No deployment, registration, or issuer opt-in — existing and future assets are exposed automatically. - Core ERC-20 plus metadata:
totalSupply,balanceOf,transfer,approve,allowance,transferFrom,name,symbol,decimals. - Polymesh issuance:
mintandburn, mapped ontoAsset::issueandAsset::redeem. - ERC-7943 compliance hooks:
canTransfer,canSend,canReceive,forcedTransfer,setFrozenTokensandgetFrozenTokens. - ERC-3643 administration:
pause,unpause,setName,setSymbol,isAgent,setAddressFrozen,freezePartialTokensandunfreezePartialTokens. - Block explorer support: the precompile serves stub Solidity bytecode, so Blockscout and similar explorers can verify each asset and render a correct Read/Write method UI.
Calls route through pallet-asset and pallet-settlement, so identity, compliance, transfer restrictions, freezes, and agent permissions are enforced exactly as for a Substrate transaction. An ERC-20 call that would break a Polymesh rule reverts.
The permissioned-token surface
ERC-3643 (T-REX) and ERC-7943 (uRWA) are the two standards that describe what a regulated, permissioned token looks like. The fungible-asset precompile implements all six ERC-7943 functions and a subset of the ERC-3643 administration surface, so tooling written against either standard needs less Polymesh-specific code. Neither is implemented in full — the reference records the deviations.
Transfer eligibility
| Method | What it answers |
|---|---|
canSend(account) | Can this account send this asset at all — compliance, frozen state, portfolio freezes? |
canReceive(account) | Can this account receive this asset at all? |
canTransfer(from, to, value) | Can this specific transfer happen right now — everything above, plus balances and transfer restrictions? |
canSend and canReceive are per-account checks that do not need an amount or a counterparty, which makes them the right choice for onboarding flows, allowlist UIs, and pre-trade validation. canTransfer, present since v8.1, remains the full pre-flight check for a specific transfer.
All three are view methods that return a boolean rather than reverting.
Freezing individual holders
| Method | Effect |
|---|---|
setAddressFrozen(account, freeze) | Freezes or unfreezes the account entirely for this asset |
freezePartialTokens(account, amount) | Freezes amount in addition to whatever is already frozen |
unfreezePartialTokens(account, amount) | Releases amount from the frozen balance |
setFrozenTokens(account, amount) | Sets the frozen balance to exactly amount |
getFrozenTokens(account) | Returns the currently frozen balance |
These are the EVM face of a new Substrate capability — before v8.1.1, freezing was all-or-nothing for the whole asset. The chain-level detail, including the portfolio case that has no EVM address, is covered in Asset Freezing.
Note the difference between freezePartialTokens and setFrozenTokens: the first is relative and the second absolute. Calling freezePartialTokens(account, 100) twice freezes 200 tokens; calling setFrozenTokens(account, 100) twice freezes 100.
Asset administration
| Method | Runs | Note |
|---|---|---|
pause() | Asset::freeze | Halts all transfers of the asset |
unpause() | Asset::unfreeze | Resumes them |
setName(name) | Asset::rename_asset | |
setSymbol(symbol) | Asset::register_unique_ticker + Asset::link_ticker_to_asset_id | Registers a new ticker and links it — see below |
isAgent(account) | reads external agent membership | view; false for an account with no identity |
setSymbol diverges most from the standard. On a typical ERC-3643 token it renames a string field. On Polymesh a ticker is a distinct, globally unique, registered object, so setSymbol registers the ticker to the caller's identity and links it to the asset. The ticker must be available, registering it charges the usual ticker fee, and a ticker already registered to somebody else causes a revert. It is at most 12 characters.
forcedTransfer now takes a recipient
forcedTransfer moves tokens without compliance checks, for the recovery and regulatory-seizure scenarios ERC-7943 exists to serve. Its signature changed in v8.1.1:
function forcedTransfer(address from, address to, uint256 amount) external returns (bool);
It delivers to a named recipient rather than to the calling agent, backed by the new Asset::controller_transfer_to extrinsic. Returning seized tokens to a rightful owner is therefore one transaction, and the tokens never rest in the agent's own custody.
The caller must still be an agent with controller-transfer permission, and the ERC-7943 ForcedTransfer event is still emitted.
Impact on the Substrate API
pallet-precompiles is not a runtime pallet. It has no pallet index, no extrinsics, no Substrate events, and does not appear in chain metadata — it plugs into pallet-revive as a config type.
The precompile methods added in v8.1.1 are, however, backed by new extrinsics that exist in their own right and are usable from any Substrate client: Asset::set_frozen_tokens, Asset::set_holder_frozen, Asset::freeze_partial_tokens, Asset::unfreeze_partial_tokens and Asset::controller_transfer_to. See Asset Freezing and the Full Pallet API Reference.
Substrate-side indexers still need no precompile-specific handling: the underlying Asset and Settlement events are emitted exactly as for any other transfer, so a transfer made through the precompile is indistinguishable from one made through an extrinsic.
Deviations from ERC-20
The ABI is standard ERC-20, but the behaviour deviates in several places that mainstream tooling depends on. Each is covered in full in ERC-20 conformance and deviations:
- Infinite approvals revert.
approve(spender, type(uint256).max)fails; Polymesh balances areu128, so the unlimited-allowance value is2^128-1. Most wallets and DEX routers sendtype(uint256).max, so this needs a Polymesh-specific path. - Zero-value and self-transfers revert (
ZeroAmount,SenderSameAsReceiver), against the ERC-20 requirement that zero transfers succeed. Guard both in your own code. - Recipients must have an active identity. Transfers to un-onboarded addresses — including
address(0), so no burn-by-transfer idiom — revert. You cannot airdrop to arbitrary EVM addresses, and a contract that custodies assets needs its own identity. - Transfers must complete atomically.
transfer/transferFromrevert if the settlement instruction cannot execute in the same call — typically when the receiver has mandatory receiver affirmation, or the asset requires mediator affirmation. Those transfers must use the Substrate settlement flow. decimals()is always6, even for indivisible assets, which silently accept only whole multiples of1_000_000.- EIP-2612
permitis declared but not implemented.permit,nonces, andDOMAIN_SEPARATORare in the ABI — so ABI-based feature detection reports the token as permit-capable — but all three revert. Fall back toapprove. balanceOfis the account-level holding, not the sum of the linked identity's portfolios, andmint/burntake no address — they always act on the calling account.
Multicall3 and CREATE2 in development chain genesis
Multicall3 (0xcA11bde05977b3631167028862bE2a173976CA11) and the deterministic-deployment CREATE2 proxy (0x4e59b44847b379578588920ca78fbf26c0b4956c) are available on Polymesh at their canonical EVM addresses, where most Ethereum tooling — viem, wagmi, ethers, Foundry — expects to find them. On live networks they were deployed manually, as ordinary contracts.
What changed in v8.1 is that development chain specs now carry them in genesis, alongside pre-mapped dev accounts (Alice through Eve), so a fresh local chain has them from block zero and you no longer need to deploy them by hand before running tooling that depends on them. The addresses and bytecode are identical either way — this is a local-setup convenience, not a new capability.
See Pre-deployed standard contracts for what these contracts do and how to confirm their presence on a given network.
Migration checklist
- Read Precompiles and Fungible Asset (ERC-20) before writing code against it.
- Derive addresses from the
AssetId; there is no registry call or index lookup. - Do not rely on
permit, and do not assume 18 decimals or a non-emptysymbol(). - Give any contract that holds or moves assets its own identity — a precompile call acts as the contract's account, not the caller's.
- Expect assets or counterparties requiring receiver or mediator affirmation to be untransferable through this interface.
- Use
canSend/canReceivefor per-account eligibility checks andcanTransferfor a specific transfer, rather than attempting a transfer and catching the revert. - If you call
setSymbol, confirm the ticker is available first and budget for the ticker registration fee. - Substrate-side indexers need no changes for the precompile itself, but see the Full Pallet API Reference for the extrinsics and events v8.1.1 adds.