The observer could read headers, events, extrinsics, metadata and difficulty, and not a single pallet storage item. Answering "which address is this chain's treasury" meant going outside it and hand-deriving twox128 prefixes, which is a gap in the tool rather than an answer. The treasury is the case that shows why. `set_treasury_account` reads *never* on the call index and `TreasuryAccountUpdated` reads *never* on the event index — both true, because the address was set at genesis, so no extrinsic ever carried it and no event ever announced it. It exists only in state. It is qzjsuLN7Nhu4bjvmUbjSTr2ZTeZ7oRxXpQP9fdv6PcHUCRrVR, and it has never been funded. `Runtime::storage_key` builds a key entirely from the runtime's own description — the pallet's storage prefix, the item name, and each key's declared hasher — because a wrong hasher yields a key that reads as *absent* rather than as an error, and nothing would catch it. Its test pins two keys against ones read off the live chain by hand. Keys are SCALE-encoded against the type the entry declares, so a map on an account, a u32 or a tuple all work without this knowing which it is. Absent is not zero, and only the modifier knows which: an `optional` entry holding nothing means nothing, a `default` entry means the runtime's default, and the state page marks the latter rather than passing it off as something the chain wrote. `read_balance` deliberately does not apply the default — `AccountInfo` zeroes, Substrate reaps empty accounts, and falling back would turn "does not exist" into "holds nothing", which is the treasury's exact case. /:chain/state lists all 40 keyless entries decoded, and an account page now carries a real balance beside the flows it already summed. Those are different numbers: rewards say what an account was paid, a balance says what it has, and they differ by every transfer out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jp6a8EDar9ueEhAxzep4V5
145 lines
5.4 KiB
TypeScript
145 lines
5.4 KiB
TypeScript
/**
|
|
* What is true right now.
|
|
*
|
|
* Everything else on this site is history — headers, events, extrinsics, all of
|
|
* it a record of what happened. This is the other half, and it answers the
|
|
* questions history cannot.
|
|
*
|
|
* The treasury is the case that made it necessary. This chain's treasury
|
|
* address was set at genesis, so no extrinsic ever carried it and no event ever
|
|
* announced it: `TreasuryPallet::set_treasury_account` shows *never* on the
|
|
* call index and `TreasuryAccountUpdated` shows *never* on the event index, and
|
|
* both are true. The address exists only in state, and until the observer could
|
|
* read state the honest answer to "which address is the treasury" was that we
|
|
* did not know.
|
|
*
|
|
* Read live on every request rather than indexed. State is what is true now,
|
|
* and a cached copy of now is a copy of some earlier now wearing the same face.
|
|
*/
|
|
|
|
import { useEffect, useState } from 'react'
|
|
|
|
import type { ChainState } from '../api/generated/ChainState'
|
|
import { RequestFailed, fetchState } from '../api/rest'
|
|
import { height as fmtHeight } from '../lib/format'
|
|
import { Payload } from './Payload'
|
|
|
|
export function StateIndex({
|
|
chain,
|
|
decimals,
|
|
symbol,
|
|
}: {
|
|
chain: string
|
|
decimals: number
|
|
symbol: string
|
|
}) {
|
|
const [state, setState] = useState<ChainState | null>(null)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
useEffect(() => {
|
|
const controller = new AbortController()
|
|
setState(null)
|
|
setError(null)
|
|
fetchState(chain, controller.signal)
|
|
.then(setState)
|
|
.catch((e: unknown) => {
|
|
if (controller.signal.aborted) return
|
|
setError(e instanceof RequestFailed ? e.message : 'Could not reach the observer.')
|
|
})
|
|
return () => controller.abort()
|
|
}, [chain])
|
|
|
|
return (
|
|
<section className="panel" style={{ marginBottom: 26 }}>
|
|
<div className="panel-head">
|
|
<h2 className="panel-title">State</h2>
|
|
{state && (
|
|
<span className="eyebrow">
|
|
{state.entries.length} entries at #{fmtHeight(state.height)}
|
|
{state.spec_version !== null && ` · runtime v${state.spec_version}`}
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
{error && <p className="empty">{error}</p>}
|
|
{!error && state === null && <p className="empty">Reading the chain…</p>}
|
|
|
|
{state !== null && (
|
|
<>
|
|
<div className="scroll-x">
|
|
<table className="board">
|
|
<caption className="visually-hidden">
|
|
Every storage entry readable without a key, decoded
|
|
</caption>
|
|
<thead>
|
|
<tr>
|
|
<th scope="col" className="left">
|
|
Entry
|
|
</th>
|
|
<th scope="col" className="left">
|
|
Value
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{state.entries.map((e) => (
|
|
<tr key={`${e.pallet}.${e.name}`}>
|
|
<td className="left">
|
|
<div className="call-cell">
|
|
<code>
|
|
{e.pallet}::{e.name}
|
|
</code>
|
|
<span className="runtime-type">{e.type_name}</span>
|
|
{e.docs && <span className="runtime-doc">{e.docs}</span>}
|
|
</div>
|
|
</td>
|
|
<td className="left">
|
|
{e.error ? (
|
|
<span className="runtime-modifier" title={e.error}>
|
|
unreadable
|
|
</span>
|
|
) : e.value === null || e.value === undefined ? (
|
|
/* An `optional` entry with nothing in it. Not zero, not
|
|
empty — the chain holds nothing there, and for the
|
|
treasury address that difference is the answer. */
|
|
<span className="runtime-modifier">unset</span>
|
|
) : (
|
|
<span className="state-value">
|
|
{typeof e.value === 'object' ? (
|
|
<Payload
|
|
value={e.value}
|
|
ctx={{ chain, addresses: {}, decimals, symbol }}
|
|
/>
|
|
) : (
|
|
<span className="numeral">{String(e.value)}</span>
|
|
)}
|
|
{e.is_default && (
|
|
<span
|
|
className="runtime-modifier"
|
|
title="Storage holds nothing here; this is the default the runtime supplies."
|
|
>
|
|
default
|
|
</span>
|
|
)}
|
|
</span>
|
|
)}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<p className="panel-note">
|
|
Read live from the chain at the block above, and decoded against the runtime's own
|
|
declaration of each entry's type — including which hash its key uses, which is why a
|
|
value appears here at all rather than as a key nobody can compute. Only entries needing
|
|
no key are listed; a map like <code>System::Account</code> is read per account, on its
|
|
own page.
|
|
</p>
|
|
</>
|
|
)}
|
|
</section>
|
|
)
|
|
}
|