Skip to main content

Balances & Transfers Changelog - v7.4 to v8.0

This page covers changes to the Balances pallet — the one most exchanges, custodians, and wallet providers interact with directly for POLYX deposits and withdrawals. It is part of the v7.4 → v8.0 changelog.

Audience: anyone parsing on-chain POLYX transfers, balances, or account storage without going through the SDK.


Overview

Chain v8 replaces Polymesh's custom Balances pallet with the standard Polkadot SDK pallet-balances, extended with a Memo type to keep transfer_with_memo working. As a result, the transfer call surface, event shapes, error variants, and the account storage layout all changed. If your integration hardcodes call indices, decodes events positionally, or assumes the v7 AccountData fields, update it before connecting to v8.

Breaking changes

1. Transfer calls replaced

v7.4 call (index)v8.0 call (index)Notes
transfer (0)(removed)Use transfer_allow_death or transfer_keep_alive
transfer_allow_death (0)New. Allows the sender account to be reaped if its balance drops to zero. With Mainnet/Testnet ExistentialDeposit = 0 and no account reaping at zero balance, this behaves like a plain transfer in practice
transfer_keep_alive (3)New. Fails if the transfer would drop the sender below the existential deposit. Functionally equivalent to transfer_allow_death under Polymesh's ED = 0 configuration
transfer_all (4)New. Transfers the sender's full transferable balance
transfer_with_memo (1)transfer_with_memo (40)Same call, new index. Signature unchanged: (dest, value, memo: Option<Memo>)
deposit_block_reward_reserve_balance (2)(removed)No direct replacement — this was specific to Polymesh's old reward-reserve model
set_balance (3)(removed)Replaced by force_set_balance (8), a root-only call with a different signature (single new_free value, no new_reserved)
force_transfer (4)force_transfer (2)Same name, new index
burn_account_balance (5)(removed)Replaced by burn (10), a self-service call: (value, keep_alive: bool) — the caller burns their own balance, rather than governance burning an arbitrary account's balance
upgrade_accounts (6)New. Migrates listed accounts to the current AccountData storage format on demand
force_unreserve (5)New, root-only. Force-unreserves an amount for an account, bypassing normal unreserve rules
force_adjust_total_issuance (9)New, root-only

Migration: for withdrawals/outgoing transfers, support at least one of transfer_allow_death, transfer_keep_alive, transfer_all, or transfer_with_memo. If your counterparties require memo-tagged deposits, use transfer_with_memo.

2. system.account storage layout change

frame_system::Account<AccountId>.data (the AccountData struct) changes shape:

// v7.4
AccountData { free, reserved, misc_frozen, fee_frozen }

// v8.0
AccountData { free, reserved, frozen, flags }

At the runtime upgrade, misc_frozen and feefrozen are combined into a single frozen value, and flags is initialized accordingly; free and reserved are untouched at that point. Full normalization to the upstream flags format (including the version bit that marks an account as "upgraded") happens lazily, the next time that specific account is written to (for example, via a transfer or staking action) — either via a runtime upgrade_accounts call or organically. Code reading raw storage should handle both the immediately-post-upgrade and fully-migrated states; the transferable-balance formula below is correct in either case.

Transferable balance:

// v7.4
transferable = free - max(misc_frozen, fee_frozen)

// v8.0, general form
transferable = free - max(ED, frozen - reserved)

// v8.0, with Polymesh Mainnet/Testnet ED = 0
transferable = free - max(0, frozen - reserved)

3. Transfer event no longer carries identity or memo fields

// v7.4
balances.Transfer(from_did: Option<IdentityId>, from: AccountId, to_did: Option<IdentityId>, to: AccountId, amount: Balance, memo: Option<Memo>)

// v8.0
balances.Transfer(from: AccountId, to: AccountId, amount: Balance)

balances.TransferWithMemo(from, to, amount, memo: Option<Memo>) is unchanged in shape and is still emitted alongside Transfer specifically when transfer_with_memo is used. This event was introduced back in v7.4 for forward compatibility with this exact v8 change, so integrations that adopted it early do not need to change their event-parsing logic now.

Deposit modelEvent to parseNotes
Memo-based deposits (shared address)balances.TransferWithMemoAvailable since v7.4; unchanged shape in v8
Unique deposit address per userbalances.TransferNew 3-field shape in v8

Parse only one event per transfer flow — transfer_with_memo emits both Transfer and TransferWithMemo for the same transfer, and double-counting will overstate deposits.

4. Full event set expanded

Chain v8's Balances pallet emits the standard upstream event set, including several events that had no v7 equivalent: DustLost, Deposit, Withdraw, Slashed, Minted, MintedCredit, Burned, BurnedDebt, Suspended, Restored, Upgraded, Issued, Rescinded, Locked, Unlocked, Frozen, Thawed, TotalIssuanceForced, Held, BurnedHeld, TransferOnHold, TransferAndHold, Released, and a defensive Unexpected event. Most of these fire for internal chain accounting (fees, staking rewards/slashing, reserve/hold management) rather than user-initiated transfers — if you only care about deposit/withdrawal accounting, Transfer and TransferWithMemo remain the events to track. One exception worth knowing: staking reward payouts now mint funds through this same deposit path, so every staking.Rewarded is accompanied by a Deposit { who, amount } telling you exactly which account was credited — see the Rewarded event section in Staking & Validators.

Endowed, BalanceSet, Reserved, Unreserved, and ReserveRepatriated all carry the same field names as v7 but as named struct fields rather than positional tuples, and no longer carry the IdentityId fields v7 attached to some of them (for example, Endowed drops the Option<IdentityId> and BalanceSet drops the IdentityId actor field). The v7.4 AccountBalanceBurned event is removed — burn_account_balance is gone (see above), and the new burn/force_adjust_total_issuance calls emit Burned/BurnedDebt/TotalIssuanceForced instead.

5. New storage and constants

Freezes, Holds, Reserves, and InactiveIssuance are all new storage items — none of the four existed at v7.4. Locks changes from an unbounded Vec<BalanceLock<Balance>> to a bounded WeakBoundedVec<BalanceLock<Balance>, MaxLocks>. New queryable constants MaxFreezes, MaxLocks, and MaxReserves cap the new (and newly-bounded) storage — at v7.4, ExistentialDeposit was the only constant Balances exposed via metadata.

6. Errors changed

The v7.4 ReceiverCddMissing error (returned when a transfer's recipient lacked a valid CDD claim) is removed, with no v8 equivalent — the standard pallet-balances has no concept of CDD. In its place, v8 adds the standard upstream error set: VestingBalance, Expendability, ExistingVestingSchedule, DeadAccount, TooManyReserves, TooManyHolds, TooManyFreezes, IssuanceDeactivated, DeltaZero, LockIdentifierNotFound, MaxLocksExceeded, alongside the carried-over LiquidityRestrictions, InsufficientBalance, ExistentialDeposit, and Overflow.

Match errors and events by name, not by numeric index — index assignments are not stable across this upgrade and several v7 and v8 variants happen to share a slot without being related.

What did not change

  • Mainnet and Testnet ExistentialDeposit remains 0. Accounts are not reaped at a zero balance, so transfer_keep_alive and transfer_allow_death behave equivalently for exchange integrations.
  • Memo-tagged transfers remain fully supported via transfer_with_memo / TransferWithMemo, unchanged from the v7.4 shape.
  • Since v7.3, receiving POLYX or staking has not required a DID — this was already true before v8 and is unaffected by this pallet swap.

Integration checklist

  1. Stop using transfer and set_balance — they no longer exist. Use transfer_allow_death/transfer_keep_alive/transfer_all and force_set_balance respectively.
  2. Update call-index tables for Balances — indices shifted for every retained call.
  3. Update balances.Transfer decoding to the 3-field shape; keep TransferWithMemo decoding as-is if you already adopted it in v7.4.
  4. Update system.account decoding for frozen/flags instead of misc_frozen/fee_frozen, and switch any transferable-balance calculation to free - max(0, frozen - reserved).
  5. Do not rely on ReceiverCddMissing as a transfer-failure signal — it no longer exists.
  6. If you match errors/events by numeric index rather than name, stop — indices are not stable identifiers across this upgrade.