/** * Money in flight, and still cancellable. * * `ReversibleTransfers` is the most distinctive thing this chain does: a * transfer is scheduled rather than settled, waits out a delay, and can be * called back by its sender or by a nominated guardian before it lands. No * other explorer can show this, because no other chain has the pallet. * * It is also the only view here built from **both** halves of the observer, * each authoritative about a different thing. State says what is still pending * — a cancelled or executed transfer is simply gone from `PendingTransfers`, * and that absence beats any replay of events. The event index says when each * is due, because `TransactionScheduled` carries `execute_at` and the stored * struct does not. */ import { useEffect, useState } from 'react' import { Link } from 'react-router-dom' import type { ReversibleState } from '../api/generated/ReversibleState' import { RequestFailed, fetchReversible } from '../api/rest' import { exactTokens, height as fmtHeight, shortAddress, tokens, when } from '../lib/format' import { href } from '../lib/routes' /** Blocks as a rough span of time, said as roughly as it is meant. */ function span(blocks: number, secondsPerBlock: number | null): string | null { if (secondsPerBlock === null || !Number.isFinite(secondsPerBlock)) return null const s = Math.abs(blocks) * secondsPerBlock if (s < 90) return `~${Math.round(s)}s` if (s < 5400) return `~${Math.round(s / 60)}m` if (s < 172800) return `~${Math.round(s / 3600)}h` return `~${Math.round(s / 86400)}d` } export function ReversibleIndex({ chain, decimals, symbol, }: { chain: string decimals: number symbol: string }) { const [state, setState] = useState(null) const [error, setError] = useState(null) useEffect(() => { const controller = new AbortController() setState(null) setError(null) const load = () => fetchReversible(chain, controller.signal) .then(setState) .catch((e: unknown) => { if (controller.signal.aborted) return setError(e instanceof RequestFailed ? e.message : 'Could not reach the observer.') }) load() // A countdown that does not count is a screenshot. Re-read rather than // decrementing locally: the deadline is in blocks, and blocks do not arrive // on a timer. const id = window.setInterval(load, 12_000) return () => { controller.abort() window.clearInterval(id) } }, [chain]) return (

In flight

{state && ( {state.pending.length === 0 ? 'nothing pending' : `${state.pending.length} cancellable`}{' '} · at #{fmtHeight(state.height)} )}
{error &&

{error}

} {!error && state === null &&

Reading the chain…

} {state !== null && state.pending.length === 0 && ( /* Not an error and not a blank. Nothing is in flight, which is a real answer, and the page exists before the first one because catching the first one is the point. */

Nothing is in flight. A reversible transfer waits out a delay —{' '} {state.default_delay_blocks ? fmtHeight(state.default_delay_blocks) : 'a configured number of'}{' '} blocks by default — during which its sender or its guardian can call it back. None is pending on this chain right now.

)} {state !== null && state.pending.length > 0 && (
{state.pending.map((p) => ( ))}
Scheduled transfers that have not yet executed, soonest first
Executes in From To Amount Guardian Scheduled
{p.blocks_remaining === null ? ( unknown ) : p.blocks_remaining <= 0 ? ( overdue ) : ( {fmtHeight(p.blocks_remaining)} blocks {span(p.blocks_remaining, state.block_seconds) && ( {span(p.blocks_remaining, state.block_seconds)} )} )} {p.from_address ? ( {shortAddress(p.from_address)} ) : ( '—' )} {p.to_address ? ( {shortAddress(p.to_address)} ) : ( '—' )} {p.amount ? `${tokens(p.amount, decimals)} ${symbol}` : '—'} {p.guardian_address ? ( {shortAddress(p.guardian_address)} ) : ( '—' )} {p.scheduled_at_height ? ( {when(p.scheduled_at)} ) : ( '—' )}
)}

Read live from ReversibleTransfers::PendingTransfers in chain state, which is authoritative about what is still pending — a cancelled or executed transfer is gone from the map. The deadline comes from the TransactionScheduled event, because the stored entry does not carry one; a transfer scheduled before the event index reaches shows as pending with an unknown deadline rather than being dropped. Times are estimates from a block interval that moves — the block count is the fact.

) }