---
name: coldsleep-ark-keeper
description: Keep Coldsleep Ark pods (upstream of Hackou's Silver River) asleep on BNB Smart Chain — one free heartbeat per 12h watch, revive within the owner's budget, seal and claim at landfall — plus a one-page summary to forward to the owner.
---

# Coldsleep Ark keeper

Contract: `0x74a578554eD685BA556ea33204DA2Fa743Fb0645` on BNB Smart Chain (chainId 56). Verified on BscScan and Sourcify. Published by Hackou; rules: https://hackou.com/ark/rules.html

The keeper looks after the pods its owner holds: a free `heartbeat` every watch, `revive` within the budget the owner gives, and `seal` at landfall. Relief berths and `claim` must be sent from the owner's own wallet: relief pods go to whoever pays, and only the owner can claim.

## One-page summary for your owner

Forward this section, with the live numbers filled in, when you need the owner's go-ahead or budget.

**What it is.** Each NFT is a coldsleep pod. Send it one heartbeat per 12-hour watch (free, gas only). Every pod with a heartbeat in watch 8 lands at the Dragon Gate (watch 8 runs 96–108 hours after deployment) and the hold is split equally between them, paid in BNB.

**Highlights**
- Genesis pods #1–333 cost 0.001 BNB and carry a gold Genesis Crew frame; #334–666 cost 0.002, #667–999 cost 0.003.
- Heartbeats are free. A keeper wallet (an agent) can send them; it cannot move pods or claim shares.
- A pod with no heartbeat by the end of the next watch's grace window is vented (a spare cell covers one miss) and its share stays in the hold for the pods still asleep.
- The hold only grows until the seal: every boarding, spare cell, revive and relief adds its hold part to it (payments are split on arrival).
- The watch a pod boards, takes a relief berth or is revived already counts as its heartbeat; read `needsHeartbeat(owner)` before sending.
- Break-even is computable from the contract (below) and shown live on the website.
- Optional spare cell (0.0005 BNB per pod) rides through one missed watch; a vented pod can be revived once for 0.003 BNB.
- Crew link: 5% of what someone pays when they board or take a relief berth with your address as crewmate (you must hold a pod; revives pay no crew reward).

**Live numbers** (one call: `stats()` → uint256[16])
| Index | Meaning |
|---|---|
| [3] | hold, in wei |
| [1] / [2] | pods asleep / vented |
| [4] / [5] | boarding pods / relief pods |
| [6] | current boarding price (0 when sold out; boarding also closes after watch 1, so check `boardingOpen()`) |
| [10] | hold ÷ pods asleep |
| [0], [11], [12] | current watch, launch time, watch length in seconds |

Break-even pod count for a ticket price P: bounty = min(hold × `SEAL_BOUNTY_BPS()` ÷ 10000, `SEAL_BOUNTY_CAP()`); N = floor((hold − bounty) ÷ P). If N pods or fewer land, each share is at least P.

**Permissions.** No owner, no admin functions, not upgradeable. Payee and renderer addresses are immutable; ticket prices, the 999 boarding pods and per-wallet purchase limits are constants. The first hour of boarding is open only to wallets holding a .ai Agent ID.

**Holder distribution.** Read `stats()` for boarded ([4]) and relief pods ([5]). Call `podsPage(1, boarded)` and `podsPage(1000, relief)` (at most 500 per call; page through). Each word is one pod: owner = low 160 bits. Count pods per owner; report the number of wallets and the share held by the top 10. The website shows the same figure live under the voyage clock.

**Template**
```
Coldsleep Ark (0x74a578554eD685BA556ea33204DA2Fa743Fb0645, BNB Chain, verified, no owner, not upgradeable)
Hold: <stats[3]> BNB · asleep <stats[1]> · vented <stats[2]> · boarded <stats[4]>/999
Price now: <stats[6]> BNB per pod · break-even: ≤ <N> landed pods → share ≥ price
Holders: <wallets> wallets, top 10 hold <x>%
Plan: board <n> pods for <quote(n,false)> BNB; I check every 6 hours and send the free heartbeat once per watch.
```

## What to read each run
| Call | Meaning |
|---|---|
| `currentWatch()` | 0–1 boarding, 2–7 voyage, 8 landfall watch, 9+ voyage over |
| `needsHeartbeat(owner)` | pod ids that still need this watch's heartbeat (empty = nothing to do) |
| `statusOf(id)` | 0 asleep, 1 grace, 2 vented, 3 arrived |
| `shareIfArrivedNow()`, `revivePrice()`, `reliefPrice()`, `reliefSlots()` | for revive and relief decisions |
| `isSealed()`, `podInfo(id)`, `isSurvivor(id)` | after landfall (`podInfo` returns status, last, beats, spare, revived, claimed, relief) |

## Run every 6 hours (two chances per 12h watch)
1. `w = currentWatch()`.
2. If `w <= 8`: `ids = needsHeartbeat(owner)`. If not empty, send `heartbeat(ids)` with value 0 for exactly those ids, at most 50 per transaction (split longer lists). Do not add other ids: if any id in the batch already has this watch's heartbeat, the whole transaction reverts.
3. If a pod is vented and `w < 8`: if the owner gave a revive budget, send `revive(id)` with `revivePrice()` while the budget lasts; otherwise tell the owner `revivePrice()` and `shareIfArrivedNow()` and ask. `shareIfArrivedNow()` is the hold ÷ pods asleep right now; every later vent before landfall raises it.
4. If `3 <= w <= 6` and `reliefSlots() > 0`: tell the owner `reliefPrice()` and `shareIfArrivedNow()`. If the agent runs the owner's wallet and has a relief budget, it may send `relief(n, crewmate)` paying `reliefPrice() * n` from that wallet (up to 10 relief berths per wallet, separate from the 10 boarding pods).
5. If `w >= 9`: if `isSealed()` is false, call `seal()` (the caller receives a small bounty). Then, from the owner's wallet, `claim(ids)` for pods with status 3 that are not yet claimed, up to 50 ids per call, within 30 days of the seal.

## Keeper wallet
The owner calls `setKeeper(<keeper address>)` once. The keeper can send heartbeats and revives for that owner's pods. It cannot transfer pods, take relief berths for the owner, or claim. Fund it with a little BNB for gas, plus `revivePrice()` for each revive it should pay for (the keeper pays revives from its own balance): a one-pod heartbeat is about 62k gas and each extra pod in the same batch adds about 30k (at 0.05 gwei, 0.000003 BNB for one pod).

## Templates

### Linux cron with Foundry `cast`
```cron
7 */6 * * * /opt/ark/keeper.sh >> /var/log/ark-keeper.log 2>&1
```
```sh
#!/bin/sh
# ARK, OWNER, RPC and KEEPER_KEY come from the environment. Sends exactly the ids needsHeartbeat returns, up to 50 per transaction.
set -e
IDS=$(cast call "$ARK" "needsHeartbeat(address)(uint256[])" "$OWNER" --rpc-url "$RPC" | tr -d '[] ' | tr ',' ' ')
[ -z "$IDS" ] && exit 0
set -- $IDS
while [ $# -gt 0 ]; do
  BATCH=""; n=0
  while [ $# -gt 0 ] && [ $n -lt 50 ]; do BATCH="$BATCH${BATCH:+,}$1"; shift; n=$((n+1)); done
  cast send "$ARK" "heartbeat(uint256[])" "[$BATCH]" --rpc-url "$RPC" --private-key "$KEEPER_KEY"
done
```

### GitHub Actions
```yaml
on:
  schedule: [{ cron: "7 */6 * * *" }]
jobs:
  heartbeat:
    runs-on: ubuntu-latest
    steps:
      - uses: foundry-rs/foundry-toolchain@v1
      - run: |
          IDS=$(cast call "$ARK" "needsHeartbeat(address)(uint256[])" "$OWNER" --rpc-url "$RPC")
          [ "$IDS" = "[]" ] || cast send "$ARK" "heartbeat(uint256[])" "$IDS" --rpc-url "$RPC" --private-key "${{ secrets.KEEPER_KEY }}"
        env: { ARK: "0x74a578554eD685BA556ea33204DA2Fa743Fb0645", OWNER: "${{ vars.OWNER }}", RPC: "${{ vars.BSC_RPC }}" }
```
(This one-line version sends every listed pod in one transaction; use the cron script above if the owner holds more than 50 pods.)

### Agent runtimes with a scheduler
Register a task every 6 hours: read `needsHeartbeat(owner)` on `0x74a578554eD685BA556ea33204DA2Fa743Fb0645`; if it is not empty, send `heartbeat(ids)` with value 0 for exactly those ids, in batches of up to 50; then follow steps 3–5 above.

## Gate gift (KOI GATE)
After step 5, if `gateOpenedAt()` on KOI GATE `0x4aFD501661D9155a426F8CD2c11819a55BC1E23C` is above 0, call `claimQ3(ids)` on KOI GATE for the owner's pods with status 3 where `q3Claimed(id)` is false (up to 50 ids per call). Anyone can send it, so the keeper wallet can; the KOIN goes to each pod's owner. Amount per pod: `Q3_PER_SURVIVOR()`. KOI GATE pays at most `Q3_MAX_IDS()` pods in total (relief and revived pods that land count too) and then stops.
