Skip to main content

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.

Reference documentation

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: mint and burn, mapped onto Asset::issue and Asset::redeem.
  • ERC-7943 compliance hooks: canTransfer, canSend, canReceive, forcedTransfer, setFrozenTokens and getFrozenTokens.
  • ERC-3643 administration: pause, unpause, setName, setSymbol, isAgent, setAddressFrozen, freezePartialTokens and unfreezePartialTokens.
  • 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​

MethodWhat 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​

MethodEffect
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​

MethodRunsNote
pause()Asset::freezeHalts all transfers of the asset
unpause()Asset::unfreezeResumes them
setName(name)Asset::rename_asset
setSymbol(symbol)Asset::register_unique_ticker + Asset::link_ticker_to_asset_idRegisters a new ticker and links it — see below
isAgent(account)reads external agent membershipview; 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 are u128, so the unlimited-allowance value is 2^128-1. Most wallets and DEX routers send type(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/transferFrom revert 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 always 6, even for indivisible assets, which silently accept only whole multiples of 1_000_000.
  • EIP-2612 permit is declared but not implemented. permit, nonces, and DOMAIN_SEPARATOR are in the ABI — so ABI-based feature detection reports the token as permit-capable — but all three revert. Fall back to approve.
  • balanceOf is the account-level holding, not the sum of the linked identity's portfolios, and mint/burn take 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​

  1. Read Precompiles and Fungible Asset (ERC-20) before writing code against it.
  2. Derive addresses from the AssetId; there is no registry call or index lookup.
  3. Do not rely on permit, and do not assume 18 decimals or a non-empty symbol().
  4. Give any contract that holds or moves assets its own identity — a precompile call acts as the contract's account, not the caller's.
  5. Expect assets or counterparties requiring receiver or mediator affirmation to be untransferable through this interface.
  6. Use canSend/canReceive for per-account eligibility checks and canTransfer for a specific transfer, rather than attempting a transfer and catching the revert.
  7. If you call setSymbol, confirm the ticker is available first and budget for the ticker registration fee.
  8. 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.