Read RungState
Every rung is one Solana account, RungState, owned by the canary_rungs program. It says whether the rung is Open or Solved and, once Solved, when it fell. Anything can read it: a Solana program in its own instruction, a backend over RPC, or a dashboard through the API. Reading it is free and needs no account with Canary.
The account
| Owner | The canary_rungs program id of this deployment, listed at https://API_ORIGIN/v1/programs |
| Address | The program-derived address of the seeds "rung" and the rung id, under that program |
| Size | 271 bytes |
| Discriminator | 67 56 8b 18 51 ff d0 fa |
| Rung | Id |
|---|---|
| Q-160 | 0 |
| Q-192 | 1 |
| Q-224 | 2 |
| C-160 | 3 |
| T-256k | 4 |
| T-255e | 5 |
The fields you need
| Offset | Field | Type | Meaning |
|---|---|---|---|
| 8 | version | u8 | 2 |
| 9 | rung_id | u8 | The rung id |
| 11 | status | u8 | 0 Open, 1 Solved |
| 143 | event_ts | i64 | Unix time of the fall: when the winning claim began |
| 151 | event_slot | u64 | The slot of the fall |
| 159 | recipient | 32 bytes | Who was paid |
| 263 | finalize_slot | u64 | The slot the bounty was paid |
Integers are little-endian. The whitepaper's Appendix A gives every field.
Read it safely
- Check the owner is the
canary_rungsprogram id. - Check the address is the rung's program-derived address.
- Check the discriminator,
version= 2 andrung_id. - If Solved, check
event_slot≤finalize_slot≤event_slot+ 300. - If any check fails, stop. Don't treat the account as Open or as Solved.
Read at finalized commitment. A rung that reads Solved stays Solved forever.
In a Solana program
With Anchor and the canary-rungs crate's cpi feature, the same way Canary's markets and vault programs read rungs:
#[derive(Accounts)]
#[instruction(rung_id: u8)]
pub struct WatchRung<'info> {
#[account(
seeds = [b"rung", &[rung_id]],
bump = rung.bump,
seeds::program = canary_rungs::ID,
)]
pub rung: Account<'info, canary_rungs::RungState>,
}
pub fn handler(ctx: Context<WatchRung>, rung_id: u8) -> Result<()> {
let rung = &ctx.accounts.rung;
require!(rung.version == 2 && rung.rung_id == rung_id, MyError::BadRung);
if rung.status == 1 {
require!(
rung.finalize_slot >= rung.event_slot && rung.finalize_slot <= rung.event_slot + 300,
MyError::BadRung
);
// The rung has fallen at rung.event_ts: react here.
}
Ok(())
}
Without Anchor, read the bytes at the offsets above after the same checks. The whitepaper's Section 14.3 has a complete function.
Over RPC
import { Connection, PublicKey } from '@solana/web3.js'
const RUNG_STATE = Buffer.from('67568b1851ffd0fa', 'hex')
export async function readRung(connection: Connection, rungsProgram: PublicKey, id: number) {
const [address] = PublicKey.findProgramAddressSync([Buffer.from('rung'), Buffer.from([id])], rungsProgram)
const account = await connection.getAccountInfo(address, 'finalized')
if (!account || !account.owner.equals(rungsProgram)) throw new Error('Not a rung')
const d = account.data
if (d.length !== 271 || !d.subarray(0, 8).equals(RUNG_STATE) || d[8] !== 2 || d[9] !== id) throw new Error('Unexpected layout')
if (d[11] === 0) return { status: 'Open' as const }
const eventSlot = d.readBigUInt64LE(151)
const finalizeSlot = d.readBigUInt64LE(263)
if (finalizeSlot < eventSlot || finalizeSlot > eventSlot + 300n) throw new Error('Outside the claim window')
return {
status: 'Solved' as const,
eventTs: new Date(Number(d.readBigInt64LE(143)) * 1000),
eventSlot,
recipient: new PublicKey(d.subarray(159, 191)).toBase58(),
}
}
To be told rather than poll, subscribe to the rung addresses with accountSubscribe. A rung's account changes only when it is funded, claimed or reclaimed from.
Through the API
curl -s https://API_ORIGIN/v1/status-feed
returns every rung's state in one document, with how fresh it is:
{
"schemaVersion": "canary.status.v1",
"freshness": { "state": "fresh", "indexedSlot": "…", "lagSlots": "…" },
"rungs": [
{ "id": "Q-160", "account": "…", "state": { "status": "Open", "eventTs": null } }
]
}
Act on the feed only when freshness.state is fresh. Each rung's account is its RungState address, so you can confirm a Solved state on chain before anything irreversible.
Which rung to watch
| You want to know | Watch |
|---|---|
| The earliest sign of a quantum computer at work | Q-160 |
| Whether it was a quantum computer, not classical money | The quantum-signal market, once C-160 is on the ladder |
| How far it has climbed | Q-192, then Q-224 |
| Whether Bitcoin and Ethereum keys can be taken | T-256k |
| Whether Solana keys can be taken | T-255e |
| When the market expects a fall | https://API_ORIGIN/v1/q-date |