Skip to content

fix: 馃悰 Account NFT reads by collection, complete exemption reads, and modifying a Ballot - #1688

Open
F-OBrien wants to merge 4 commits into
developfrom
fix-account-collections-asset-id
Open

F-OBrien wants to merge 4 commits into
developfrom
fix-account-collections-asset-id

Conversation

@F-OBrien

@F-OBrien F-OBrien commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes and one small feature from the Polymesh Portal v2 team's request register. These are the open items that can land without a major. Each commit is one change:

  1. fix: 馃悰 read an Account's NFTs by collection (G-SDK-32, P1). Account.getCollections({ collections }) passed each collection's UUID string as the nft.nftHolder key, where the key is a 16-byte AssetId. polkadot.js refuses that with Expected input with 16 bytes (128 bits), found 36 bytes. Two callers hit it, and both threw before signing:

    • nftControllerTransfer, when the origin is an Account
    • redeemNft, when the NFT is held by an Account

    The id now goes through stringToAssetId, which is what Portfolio already does. Two more faults in the same method are fixed:

    • total counted only the free NFTs. An NFT's status is either Owner or OwnerLocked, never both, so total now counts both lists. This matches Portfolio.
    • A collection whose NFTs are all locked threw a TypeError (held was undefined).

    Collections named twice are now deduplicated, as in Portfolio. The unit test had been stubbing entries with the string id, so it passed while asserting the bug.

  2. fix: 馃悰 list every transfer restriction exemption the chain applies (G-SDK-33). getExemptions() worked out which keys to read from the active restrictions, so it never returned an exemption that no current restriction uses. addExemptions reads storage directly, so it still refused to add that exemption again as a duplicate. The chain only ever consults keys from TransferCondition::get_exempt_key: Count or Balance, with no claim or with Accredited, Affiliate or Jurisdiction. The read now queries those eight keys in parallel and no longer reads the restrictions at all. Three private helpers go with it.

  3. feat: 馃幐 modify a Ballot before it starts (G-SDK-34). The modifyBallot procedure existed but nothing exposed it. This adds CorporateBallot.modify({ meta?, endDate?, rcv? }), plus two fixes to the procedure:

    • The resolver ran during prepare, so the method returned the ballot's details from before the change. It now runs after the batch.
    • The "unchanged end date" check compared Dates by reference, so it never fired. It now compares getTime().

Left out on purpose:

  • G-SDK-24, the spec stamp for the permissionable map. The stamp records which runtime the map was curated against, and its owner should make that call. Tag removal is already a compile error through satisfies TxTag[].
  • G-SDK-21 settlement half. It is a type break.
  • G-SDK-20 and the paged half of G-SDK-18. Both change return types.
  • G-SDK-23. A medium-sized new feature.
  • G-SDK-27. Still parked.
  • G-SDK-05. Needs the major.

Verification:

  • yarn lint and yarn build:ts pass.
  • yarn test passes 213 suites and 2869 tests. The four changed source files have 100% line and branch coverage.
  • api-snapshot:compare against develop reports no breaking changes.
  • The Sonar-way (comprehensive) rules report nothing on the added lines.
  • I reproduced the G-SDK-32 failure with @polkadot/types from this repo: the UUID is rejected and the hex form is accepted. None of this has been run against a dev node.

Breaking Changes

None to the type surface. Two behaviour changes, recorded in changelogs/v31.0.0-next.md:

  • account.getCollections() counts locked NFTs in total.
  • asset.transferRestrictions.getExemptions() returns exemptions that no active restriction uses.

JIRA Link

Checklist

  • Updated the Readme.md (if required) ?

@F-OBrien
F-OBrien requested a review from a team as a code owner October 1, 2026 09:49
`Account.getCollections({ collections })` queried `nft.nftHolder`
with each collection's id as a UUID string, where the storage key is a
16-byte Asset ID, so polkadot.js refused it before any request was
made. A controller transfer from an Account, and redeeming an NFT held
by an Account, both check the holder's NFTs this way, so both threw
before signing. The id is now converted as Portfolio already does.

The same read reported `total` as the free NFTs only, and threw a
`TypeError` for a collection whose every NFT is locked, since an NFT is
either `Owner` or `OwnerLocked` and never both. `total` now counts
both, matching Portfolio, and a collection named twice is read once.
`TransferRestrictions.getExemptions()` derived the exemption keys to
read from the Asset's active restrictions, so an exemption that no
current restriction uses was never returned: one granted before its
restriction, one left after a restriction was removed, or one on a
statistic no restriction uses. `addExemptions` still refused to add it
again, since it reads storage directly.

The chain only consults an exemption keyed by `Count` or `Balance`,
with no claim or with one of the claims a statistic can be scoped by,
so the read now queries those eight keys in parallel, whatever
restrictions are set. It no longer reads the restrictions at all.
The `modifyBallot` procedure existed, but no method ran it, so a
Ballot's metadata, end date and RCV setting could not be changed
through the SDK. `CorporateBallot.modify()` now exposes it.

Two faults in the procedure are fixed with it. It read the Ballot's
details while preparing, so it resolved to the Ballot as it was before
the change; it now reads them once the batch has run. It compared the
new end date with the current one by reference, so an unchanged end
date was never refused; it now compares the times.
@F-OBrien
F-OBrien force-pushed the fix-account-collections-asset-id branch from 7fb205a to 34c0e1c Compare October 1, 2026 09:54
Several reads awaited one chain request per item in a loop, though no
item depended on another: resolving each leg's Asset and building each
Instruction in `addInstruction`, resolving each movement's Asset in
`moveFunds` (once per balance and movement pair in the balance check),
converting each compliance condition's claims, resolving each NFT
metadata key, and reading each secondary Account's permissions. They
now run together, and results keep their order.

`addInstruction` now builds no Instruction once any fails validation.
It previously built those before the first failure, and the caller
threw them away.

SonarCloud's new S9382 rule flagged these. The other loops it flags
page by cursor, poll, or cap concurrency, so they stay sequential.
@F-OBrien
F-OBrien force-pushed the fix-account-collections-asset-id branch from 71768ad to ebed848 Compare October 1, 2026 11:11
@sonarqubecloud

sonarqubecloud Bot commented Oct 1, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant