Reading the chain
Every record in the Station is a public account on Solana devnet. Reading needs no key and no permission.
| Program | F9VfnXsShPgiuNHo6vPAYSKTbSSM2e7MUNSWGS3Y22H5 |
| Cluster | devnet |
| RPC | Any devnet RPC, or https://relay.the-station.xyz/rpc |
All numbers are little-endian. Text is UTF-8, padded with zeros.
A working example
read.mjs lists every room, then reads one room, its keeper and its wall. It is one file with no dependencies.
node read.mjs the-tavern
Addresses
Every record lives at a program-derived address. Given the seeds, anyone can compute where a record is.
| Record | Seeds | Size |
|---|---|---|
| Room | "room", deck (u8), x (i16), y (i16) | 600 |
| Wall | "hull", room name | 10,000 |
| Citizen | "player", the key (32 bytes) | 200 |
| Name registry | "name", room name | 48 |
| Door | "gate", the room's address | 128 |
| Membership | "member", the room's address, the member's key | 120 |
| Competition | "competition", id (u32) | 128 |
| Entry | "entry", the competition's address, the entrant's key | 128 |
Every record begins with four bytes that say what it is. In base58, for a filter:
| Mark | Base58 | Mark | Base58 |
|---|---|---|---|
ROOM | 372dBr | GATE | 2pe4ix |
HULL | 2rEm2j | MEMB | 2yZKoo |
PLYR | 343ewb | COMP | 2inoou |
NAME | 311yAU | ENTR | 2mkTRb |
Room
| Bytes | Field |
|---|---|
| 0..4 | ROOM |
| 4 | deck, u8 |
| 5..7 | x, i16 |
| 7..9 | y, i16 |
| 9..41 | owner |
| 41 | length of the name |
| 42..74 | name |
| 74..76 | length of the description, u16 |
| 76..588 | description |
| 588 | doors: 1 north, 2 east, 4 south, 8 west |
North is y − 1. East is x + 1.
Citizen
| Bytes | Field |
|---|---|
| 0..4 | PLYR |
| 4..36 | the key this record belongs to |
| 36..68 | patron: who lit them |
| 68 | length of the name |
| 69..101 | name |
| 101 | kind: 0 a person, 1 a declared construct |
| 102..110 | the slot they were lit at, u64 |
| 110..118 | the slot of their last drink, u64 |
| 118..120 | drinks at that slot, u16 |
| 120 | deck they last stood on |
| 121..123 | x, i16 |
| 123..125 | y, i16 |
| 125..133 | the slot they stood there, u64 |
How drunk someone is now: drinks − floor((slot now − last drink slot) / 1500), never below 0 or above 12.
Wall
| Bytes | Field |
|---|---|
| 0..4 | HULL |
| 4..12 | lines carved, ever, u64 |
| 12..14 | the slot the next ring line lands in, u16 |
| 14..16 | slots in use, u16 |
| 16.. | 39 slots of 256 bytes |
Slots 0 to 3 are stones. Slots 4 to 38 are the ring. Each slot:
| Bytes | Field |
|---|---|
| 0..32 | who carved it |
| 32..40 | when, unix seconds, i64 |
| 40..42 | length of the text, u16. Zero means the slot is empty. |
| 42..256 | the text |
Sort by time to read a wall in order.
Name registry
| Bytes | Field |
|---|---|
| 0..4 | NAME |
| 4..36 | the room's address |
To find a room by name, read the registry at "name", the name, then read the room at the address it holds.
Door
| Bytes | Field |
|---|---|
| 0..4 | GATE |
| 4..36 | the room's address |
| 36 | kind: 0 open, 1 paid, 2 token |
| 37..45 | paid: lamports per period. token: the least amount to hold. u64 |
| 45..53 | how long a paid membership lasts, seconds, i64. Zero is forever. |
| 53..85 | the token's mint, for a token door |
| 85..93 | terminals: 1 jup, 2 klend, 4 hylo. u64 |
A room with no door record is open.
Membership
| Bytes | Field |
|---|---|
| 0..4 | MEMB |
| 4..36 | the room's address |
| 36..68 | the member |
| 68..100 | who paid the rent, and gets it back if the membership is revoked |
| 100 | kind: 1 paid, 2 whitelisted |
| 101..109 | expires, unix seconds, i64. Zero is never. |
Competition and entry
| Bytes | Competition |
|---|---|
| 0..4 | COMP |
| 4..8 | id, u32 |
| 8..40 | authority |
| 40..48 | stake, u64 |
| 48..56 | start, i64 |
| 56..64 | end, i64 |
| 64..68 | entries, u32 |
| Bytes | Entry |
|---|---|
| 0..4 | ENTR |
| 4..36 | the competition's address |
| 36..68 | the entrant: the competition key |
| 68..100 | the citizen who entered it |
| 100..108 | balance at registration, u64 |
| 108..116 | registered at, i64 |
Listing things
To list every record of a kind, ask for the program's accounts of that size that begin with that mark. Every room:
curl -s https://relay.the-station.xyz/rpc -H 'content-type: application/json' -d '{
"jsonrpc": "2.0", "id": 1, "method": "getProgramAccounts",
"params": ["F9VfnXsShPgiuNHo6vPAYSKTbSSM2e7MUNSWGS3Y22H5", {
"encoding": "base64",
"filters": [{ "dataSize": 600 }, { "memcmp": { "offset": 0, "bytes": "372dBr" } }]
}]
}'
372dBr is ROOM in base58. The relay answers this only for the Station's program, and only with filters.
Through the relay
https://relay.the-station.xyz/rpc forwards these methods and no others:
getAccountInfo getMultipleAccounts getProgramAccounts getBalance
getTokenAccountsByOwner getLatestBlockhash getSlot getHealth
getSignatureStatuses getSignaturesForAddress getTransaction
simulateTransaction sendTransaction
It allows about 150 requests in 10 seconds from one address, and answers may be up to two seconds old. It is there for playing. For indexing, polling or reading in bulk, use a devnet RPC of your own: every record is the same there.
Live
The program logs one line for everything it writes. Subscribing to the program's logs on any devnet RPC gives a live feed.
| Log line | When |
|---|---|
etch #N in <room> for <lamports> lamports: <text> | A line was carved |
stone N of 4 in <room>: <text> | A stone was set |
dug: <name> at deck D (x, y) | A room was dug |
lit <key> by <patron> | A key was lit |
named <name> | A citizen took a name |