Skip to main content
Version: 31.0.x

SDK Changelog - v30.1.0 to v30.2.0

This guide describes changes between v30.1.0 and v30.2.0 of @polymeshassociation/polymesh-sdk.


Overview

v30.2.0 is a minor release containing new features and bug fixes. There are no breaking changes — upgrading is a drop-in replacement for v30.1.0.

This release adds registrar-gated DID registration, portfolio-level asset pre-approval, and several new settlement/asset getters. It also fixes transaction signing with Ledger hardware wallets using the Polkadot Generic Ledger app, corrects the POLYX balance calculation for chain v8 accounts with staked (held) funds, and fixes v8 off-chain settlement receipt expiry propagation and encoding. No code changes are required in consuming applications, though note the clarified meaning of locked in AccountBalance described below.


New features

registerDid — registrar-gated DID registration (v8 only)

Implements the v8-only identity.registerDid extrinsic, letting an active DID Registrar register a DID for someone else's Account:

const identity = await sdk.identities.registerDid({ targetAccount: '5G...' });

This complements the existing permissionless Identities.selfRegisterDid and the deprecated registerIdentity (cddRegisterDid), which no longer attaches a CDD claim as of v8. Calling this on a v7 chain, or targeting an Account that already has an Identity, raises a PolymeshError.

Portfolio-level asset pre-approval

A Portfolio can now pre-approve receiving a specific asset, so incoming transfers of it auto-affirm without a manual affirm step — the portfolio-level counterpart to the existing Identity-level pre-approval:

await portfolio.preApproveAsset({ asset });
await portfolio.isAssetPreApproved(asset); // true
await portfolio.removeAssetPreApproval({ asset });

Portfolio.preApprovedAssets getter

Lists all assets a Portfolio has pre-approved, paginated over the preApprovedPortfolios storage entries, mirroring Identity.preApprovedAssets.

Settlement getters: leg status and venue signer count

  • Instruction.getLegStatus({ legId }) — returns the execution status of a specific leg in an Instruction.
  • Venue.getSignerCount() — returns the number of signers allowed by a Venue.

unlockInstructionForExecution — complete the lock/relock cycle

Instruction.unlockForExecution() moves a LockedForExecution instruction back to Pending, and Instruction.getRelockStatus() exposes the mediator's last unlock timestamp, relock count, max relock count, and the on-chain relock cooldown window — completing the lock/relock flow that lockForExecution alone couldn't finish.

Ticker length validated against live chain config

reserveTicker, createAsset, and createNftCollection now validate the ticker length against the chain's live TickerConfig.maxTickerLength instead of a hardcoded constant, so the SDK stays correct if the chain's configured max length changes.

BaseAsset.getIssuedInFundingRound

Returns the total amount of an Asset issued in a given funding round:

const issued = await asset.getIssuedInFundingRound('Series A');

Assets.getTickerRegistrationConfig

Returns the chain-wide rules used to validate ticker registrations — maxTickerLength and registrationLength (null if registrations don't expire).


Bug fixes

Correct v8 off-chain settlement receipt expiry and encoding

  • expiresAt was computed and signed into the off-chain receipt payload but dropped before reaching affirmWithReceipts/affirmWithReceiptsWithCount — it's now forwarded into both the returned OffChainAffirmationReceipt and the raw on-chain receipt details.
  • The v8 signed payload now includes the chain's genesis_hash and length-prefixes the receipt label, matching the runtime's ChainScopedMessage encoding.
  • Fixed the byte order of the signed expiresAt value (it was missing the little-endian flag).
  • signatureToMeshRuntimeMultiSignature now uses correctly sized [u8; 64]/[u8; 65] byte arrays instead of a bare U8aFixed, which failed to resolve against a v8 chain's metadata registry.

No action is needed from consumers — off-chain receipt affirmation on v8 chains simply works correctly now.

Support signers that return a signed transaction (Ledger generic app)

Signing any transaction with a Ledger device via the Polkadot Generic Ledger app failed with:

The `signedTransaction` field may not be submitted when `withSignedTransaction` is disabled

The generic Ledger app requires the CheckMetadataHash signed extension, which means the wallet must rebuild the transaction (setting mode and metadataHash) before the device will sign it. The wallet then returns the rebuilt extrinsic in SignerResult.signedTransaction, which the Polkadot API rejects unless the caller opts in.

The SDK now passes withSignedTransaction: true when signing, so signers that return a rebuilt signedTransaction are accepted and the rebuilt extrinsic is what gets submitted to the chain.

Details:

  • No impact on other signers — software wallets and local keypairs return only a signature and behave exactly as before; the option merely permits the signedTransaction field, it does not request it.
  • Call data is protected — the SDK also sets allowCallDataAlteration: false, so a returned signedTransaction may only alter signed extensions (e.g. mode, metadataHash, era, nonce, tip). Any submission where the signer changed the transaction call itself is rejected with an error.
  • Correct hash tracking — because a rebuilt extrinsic can have a different hash than the one the SDK composed, the SDK now tracks the hash of the transaction actually submitted to the node. txHash on the transaction object always reflects the on-chain extrinsic.

No migration steps are needed — transactions signed with Ledger devices simply work.

Correct POLYX balance calculation for chain v8 accounts with holds

On chain v8, funds bonded for staking moved from locks on the free balance to holds tracked in reserved, and the single frozen value can legitimately exceed free (frozen funds may overlap with held funds). The SDK still calculated the spendable balance with the pre-v8 formula free - frozen, which produced negative free balances for staking accounts .

Account.getBalance (and accountBalance internally) now follows the chain's rules:

free = chain free - max(frozen - reserved, existential deposit)

clamped at zero, with the existential deposit (0.000001 POLYX) read from the chain rather than hardcoded.

Field semantics (clarified):

  • free — balance guaranteed to be spendable on transfers and fees. The existential deposit is always excluded, so a transaction paying up to free will never fail for balance reasons.
  • locked — everything unavailable: funds on hold (e.g. bonded for staking), frozen funds not covered by holds (e.g. vesting) and the existential deposit. Always equal to total - free. Note: previously this only reflected the chain's frozen value; it now also includes reserved funds, so staking accounts will report a larger locked value than before — this is the correct interpretation under v8 semantics.
  • total — all funds owned by the Account: free + locked (the chain's free + reserved).

New fields on AccountBalance expose the underlying chain values for applications that want to present more detail:

  • reserved — funds placed on hold by the protocol (e.g. POLYX bonded for staking), released when e.g. unbonded and withdrawn.
  • frozen — the minimum balance (out of total) that must remain in the Account due to freezes/locks (e.g. vesting). May overlap with reserved: vested POLYX that is also bonded counts towards both. On v7 chains this is max(miscFrozen, feeFrozen).

These additions are backwards compatible for code that reads balances. Validations that consume free (transferPolyx, bondPolyx, transaction fee checks) now use the corrected value, so they no longer reject valid transactions for staking-heavy accounts.


Version compatibility matrix

SDK versionChain v7Chain v8Middleware V2Polkadot.js
v30.1.0≥ v19.6.0-alpha.216.5.2
v30.2.0≥ v19.6.0-alpha.216.5.2