The extension needs to read chain state: an account's free balance for the
account list, and its nonce to build the next extrinsic. Tier 1
(quantus/extension#7) side-stepped both — it takes the nonce from system_accountNextIndex, an RPC that happens to exist, and never shows a
balance. Neither dodge survives contact with the UI.
What is needed
Storage addressing, read from the metadata the same way everything in #3 is:
storageKey(pallet, item, keys) — twox128(pallet) ‖ twox128(item) and then
each key hashed by the hasher the runtime declares for it. The hashers are in
the metadata; nothing here should know that System::Account is Blake2_128Concat.
decodeStorage(pallet, item, bytes) — against the entry's declared value type.
The default for a Default entry, which is what the chain means when state_getStorage returns nothing. An Optional entry means nothing when it
returns nothing, and conflating the two reads an unfunded account as an error
rather than as zero.
blackbeard.observer's StorageTarget and StorageMap in crates/blackbeard-core/src/runtime.rs are the reference; they already carry the
default-vs-optional distinction and the "which hashers keep their key" table.
twox128 is a new dependency (twox-hash), and Blake2_128Concat needs blake2 —
both already in blackbeard's graph.
Acceptance
The extension's account list shows a real Heisenberg balance for qzk1Nxai…2vSn7 matching what the chain reports, and the tier-1 harness gets its
nonce from System::Account rather than from an RPC convenience method.
Fee estimation (TransactionPaymentApi_query_info) is a runtime call rather than
a storage read; it needs state_call and a return type decoded from the metadata,
and is worth its own issue once this lands.
Follow-up to #3, which covers extrinsics only.
The extension needs to read chain state: an account's free balance for the
account list, and its nonce to build the next extrinsic. Tier 1
(quantus/extension#7) side-stepped both — it takes the nonce from
`system_accountNextIndex`, an RPC that happens to exist, and never shows a
balance. Neither dodge survives contact with the UI.
## What is needed
Storage addressing, read from the metadata the same way everything in #3 is:
- `storageKey(pallet, item, keys)` — `twox128(pallet) ‖ twox128(item)` and then
each key hashed by the hasher the runtime declares for it. The hashers are in
the metadata; nothing here should know that `System::Account` is
`Blake2_128Concat`.
- `decodeStorage(pallet, item, bytes)` — against the entry's declared value type.
- The **default** for a `Default` entry, which is what the chain means when
`state_getStorage` returns nothing. An `Optional` entry means nothing when it
returns nothing, and conflating the two reads an unfunded account as an error
rather than as zero.
`blackbeard.observer`'s `StorageTarget` and `StorageMap` in
`crates/blackbeard-core/src/runtime.rs` are the reference; they already carry the
default-vs-optional distinction and the "which hashers keep their key" table.
`twox128` is a new dependency (`twox-hash`), and `Blake2_128Concat` needs blake2 —
both already in blackbeard's graph.
## Acceptance
The extension's account list shows a real Heisenberg balance for
`qzk1Nxai…2vSn7` matching what the chain reports, and the tier-1 harness gets its
nonce from `System::Account` rather than from an RPC convenience method.
Fee estimation (`TransactionPaymentApi_query_info`) is a runtime call rather than
a storage read; it needs `state_call` and a return type decoded from the metadata,
and is worth its own issue once this lands.
storageTarget(pallet, item, keys) returns { key, valueTy, default } — the key being twox128(prefix) ‖ twox128(item) plus each map key hashed by the hasher the entry declares, with the number of keys checked against the number of hashers. decodeStorage(valueTy, bytes) reads the value. Nothing in it knows that System::Account is a Blake2_128Concat map over an AccountId32; the prefix, hashers, key types, value type and default all come out of the metadata.
The Default/Optional distinction is carried as asked: default is hex for a Default entry and null for an Optional one, so an account nobody has funded reads as a zero balance rather than as a failure.
Proven against Heisenberg through quantus/extension's tier-1 harness, which now reads real balances and asserts they moved — including catching that two batch_all calls it had reported as reverted had in fact funded their targets with exactly 50 HEI each. Unit tests pin the key layout (16+16+16+32 for System::Account) and the declared default decoding to a zero balance, against real spec-148 metadata.
Closing short of one stated acceptance criterion, deliberately. The extension's account list does not yet show a balance — that is extension-side UI work with its own issue to be written, not a gap in this package. The tier-1 harness also still takes its nonce from system_accountNextIndex rather than System::Account, and that turns out to be the correct call for choosing a nonce: it counts pending pool transactions, which is exactly what a signer wants. It is the wrong call for waiting on inclusion, and the harness no longer uses it for that — see quantus/extension#7.
One other thing came out of the same work and is worth noting here, since it is the same package: a multi-field variant with named fields only accepted a positional array, so Utility.batch_all — whose Vec<RuntimeCall> holds calls spelled exactly like top-level ones — could not be encoded at all. Nested calls now take the object form at any depth.
Shipped in `@quantus/codec` 0.4.0.
`storageTarget(pallet, item, keys)` returns `{ key, valueTy, default }` — the key being `twox128(prefix) ‖ twox128(item)` plus each map key hashed by the hasher the **entry declares**, with the number of keys checked against the number of hashers. `decodeStorage(valueTy, bytes)` reads the value. Nothing in it knows that `System::Account` is a `Blake2_128Concat` map over an `AccountId32`; the prefix, hashers, key types, value type and default all come out of the metadata.
The `Default`/`Optional` distinction is carried as asked: `default` is hex for a `Default` entry and `null` for an `Optional` one, so an account nobody has funded reads as a zero balance rather than as a failure.
Proven against Heisenberg through `quantus/extension`'s tier-1 harness, which now reads real balances and asserts they moved — including catching that two `batch_all` calls it had reported as reverted had in fact funded their targets with exactly 50 HEI each. Unit tests pin the key layout (16+16+16+32 for `System::Account`) and the declared default decoding to a zero balance, against real spec-148 metadata.
**Closing short of one stated acceptance criterion, deliberately.** The extension's account list does not yet show a balance — that is extension-side UI work with its own issue to be written, not a gap in this package. The tier-1 harness also still takes its nonce from `system_accountNextIndex` rather than `System::Account`, and that turns out to be the *correct* call for choosing a nonce: it counts pending pool transactions, which is exactly what a signer wants. It is the wrong call for waiting on inclusion, and the harness no longer uses it for that — see quantus/extension#7.
One other thing came out of the same work and is worth noting here, since it is the same package: a multi-field variant with named fields only accepted a positional array, so `Utility.batch_all` — whose `Vec<RuntimeCall>` holds calls spelled exactly like top-level ones — could not be encoded at all. Nested calls now take the object form at any depth.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Follow-up to #3, which covers extrinsics only.
The extension needs to read chain state: an account's free balance for the
account list, and its nonce to build the next extrinsic. Tier 1
(quantus/extension#7) side-stepped both — it takes the nonce from
system_accountNextIndex, an RPC that happens to exist, and never shows abalance. Neither dodge survives contact with the UI.
What is needed
Storage addressing, read from the metadata the same way everything in #3 is:
storageKey(pallet, item, keys)—twox128(pallet) ‖ twox128(item)and theneach key hashed by the hasher the runtime declares for it. The hashers are in
the metadata; nothing here should know that
System::AccountisBlake2_128Concat.decodeStorage(pallet, item, bytes)— against the entry's declared value type.Defaultentry, which is what the chain means whenstate_getStoragereturns nothing. AnOptionalentry means nothing when itreturns nothing, and conflating the two reads an unfunded account as an error
rather than as zero.
blackbeard.observer'sStorageTargetandStorageMapincrates/blackbeard-core/src/runtime.rsare the reference; they already carry thedefault-vs-optional distinction and the "which hashers keep their key" table.
twox128is a new dependency (twox-hash), andBlake2_128Concatneeds blake2 —both already in blackbeard's graph.
Acceptance
The extension's account list shows a real Heisenberg balance for
qzk1Nxai…2vSn7matching what the chain reports, and the tier-1 harness gets itsnonce from
System::Accountrather than from an RPC convenience method.Fee estimation (
TransactionPaymentApi_query_info) is a runtime call rather thana storage read; it needs
state_calland a return type decoded from the metadata,and is worth its own issue once this lands.
Shipped in
@quantus/codec0.4.0.storageTarget(pallet, item, keys)returns{ key, valueTy, default }— the key beingtwox128(prefix) ‖ twox128(item)plus each map key hashed by the hasher the entry declares, with the number of keys checked against the number of hashers.decodeStorage(valueTy, bytes)reads the value. Nothing in it knows thatSystem::Accountis aBlake2_128Concatmap over anAccountId32; the prefix, hashers, key types, value type and default all come out of the metadata.The
Default/Optionaldistinction is carried as asked:defaultis hex for aDefaultentry andnullfor anOptionalone, so an account nobody has funded reads as a zero balance rather than as a failure.Proven against Heisenberg through
quantus/extension's tier-1 harness, which now reads real balances and asserts they moved — including catching that twobatch_allcalls it had reported as reverted had in fact funded their targets with exactly 50 HEI each. Unit tests pin the key layout (16+16+16+32 forSystem::Account) and the declared default decoding to a zero balance, against real spec-148 metadata.Closing short of one stated acceptance criterion, deliberately. The extension's account list does not yet show a balance — that is extension-side UI work with its own issue to be written, not a gap in this package. The tier-1 harness also still takes its nonce from
system_accountNextIndexrather thanSystem::Account, and that turns out to be the correct call for choosing a nonce: it counts pending pool transactions, which is exactly what a signer wants. It is the wrong call for waiting on inclusion, and the harness no longer uses it for that — see quantus/extension#7.One other thing came out of the same work and is worth noting here, since it is the same package: a multi-field variant with named fields only accepted a positional array, so
Utility.batch_all— whoseVec<RuntimeCall>holds calls spelled exactly like top-level ones — could not be encoded at all. Nested calls now take the object form at any depth.