CanaryDocsRead RungState

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

OwnerThe canary_rungs program id of this deployment, listed at https://API_ORIGIN/v1/programs
AddressThe program-derived address of the seeds "rung" and the rung id, under that program
Size271 bytes
Discriminator67 56 8b 18 51 ff d0 fa
RungId
Q-1600
Q-1921
Q-2242
C-1603
T-256k4
T-255e5

The fields you need

OffsetFieldTypeMeaning
8versionu82
9rung_idu8The rung id
11statusu80 Open, 1 Solved
143event_tsi64Unix time of the fall: when the winning claim began
151event_slotu64The slot of the fall
159recipient32 bytesWho was paid
263finalize_slotu64The slot the bounty was paid

Integers are little-endian. The whitepaper's Appendix A gives every field.

Read it safely

  1. Check the owner is the canary_rungs program id.
  2. Check the address is the rung's program-derived address.
  3. Check the discriminator, version = 2 and rung_id.
  4. If Solved, check event_slot ≤ finalize_slot ≤ event_slot + 300.
  5. 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 knowWatch
The earliest sign of a quantum computer at workQ-160
Whether it was a quantum computer, not classical moneyThe quantum-signal market, once C-160 is on the ladder
How far it has climbedQ-192, then Q-224
Whether Bitcoin and Ethereum keys can be takenT-256k
Whether Solana keys can be takenT-255e
When the market expects a fallhttps://API_ORIGIN/v1/q-date