Two jobs, both dull on purpose.
1. Keep the seed queue full. A keccak256(secret) commitment has to be on
chain before the pack that consumes it is bought. That is the entire fairness
argument, so the service tops the queue up whenever it drops below
SEED_FLOOR, and buy reverts rather than borrowing against an empty queue.
2. Reveal every pack. It recomputes the draw from the secret, quotes each
swap through the Uniswap quoter, and sends reveal with a per-coin minimum. A
stale quote reverts the whole reveal; it requotes on the next pass.
It cannot steal a pack. It can only reveal the committed seed or fail to, and
failing to hands the buyer their refund once revealWindow has passed.
npm install
cp .env.example .env # fill it in
npm run check # the keeper's draw vs the contract's. Must pass.
npm startnpm run check needs no chain and no keys. Run it first, every time. It is the
cheapest way to find out that src/draw.js and ShellrPacks.sol have drifted,
and the alternative way to find out is every reveal reverting on mainnet.
src/draw.js is a line-for-line mirror of ShellrPacks._count, _pick and
_payout. The keeper has to know exactly which coins a pack will land on
before it sends the reveal, because it quotes each swap and passes the
minimums in.
If this file and the contract ever disagree, the reveal reverts on the first slippage check and every pack stalls until its refund window. Loudly, at least - which is why the minimums are per coin rather than one number for the pack.
src/stocks.js is the stock-pack keeper and derives its own master from the
one in .env - keccak256(master ++ "stocks"). Not the same value. Both
contracts number their commit queues from zero, so sharing a master would make
stock pack #5 and meme pack #5 share a secret, and ShellrPacks publishes its
secret on reveal. See
shellr-stock-packs.
Any change to the draw in the contract has to be made here in the same
commit. The same mirror exists a third time in
@shellr/sdk, where it is what
lets anyone verify a settled pack. Three copies, one source of truth, and CI on
all three.
pack N failed: ... repeating. Almost always a coin configured with a fee
tier that has no pool. Run npm run pools, fix it with setCoin, and the stuck
packs settle on the next pass - or refund themselves if you are slow.
npm run check failing. src/draw.js and ShellrPacks.sol have drifted.
Do not restart the keeper until they agree; a mismatch means it quotes the wrong
coins and every reveal reverts.
npm start |
the keeper |
npm run check |
draw vectors, no chain needed |
npm run wallet |
a fresh keypair, printed and not saved |
npm run pools |
which coins have a v3 pool, at which fee tier, with what depth |
npm run coin -- 0x… |
the same question about any one token, for finding a replacement |
npm run deploy |
compiles and deploys ShellrPacks. Dry run unless CONFIRM=1 |
npm run deploy:stocks |
the same for ShellrStockPacks |
npm run configure |
finds each coin's deepest pool and calls addCoin. Dry run unless CONFIRM=1 |
npm run inspect [id] |
what happened to a pack: settled, delivered, or stuck |
npm run bankroll |
fund or read the house bankroll |
npm run limits -- <min> <max> [fee %] |
change what a pack may cost. Dry run unless CONFIRM=1 |
npm run tokensales |
the $SHELLR rate, the daily cap, and the off switch |
npm run staking |
fund and read ShellrStaking |
npm run pause / npm run pause -- off |
stop new buys and sell-backs |
Every script that sends a transaction is a dry run by default and prints
what it would do. CONFIRM=1 is the difference. Deploying needs no Foundry.
The keeper has to be up whenever packs can be bought. A pack nobody opens is a buyer waiting out a ten minute refund window, so a laptop with a terminal on it is not where this belongs.
-
New Project → Deploy from GitHub repo, this repository. The
Dockerfileis picked up automatically; the service opens no port, which is why the Dockerfile is explicit rather than left to the builder's guess. -
Set the variables under the service's Variables tab. None may be missing:
PACKS_ADDRESS the deployed contract KEEPER_KEY the keeper wallet's private key MASTER_SECRET the seed master - the SAME value as before, or every committed seed already on chain becomes unrevealable and those packs can only be refunded RPC_URL one or more URLs, comma separated SLIPPAGE_BPS 300 SEED_FLOOR 40 SEED_BATCH 80 SEED_CHUNK 20 POLL_MS 1500 LOG_WINDOW 400 -
Watch the deploy log for the four lines it prints on start: keeper address, contract, WETH, and gas. If gas is low, top the keeper wallet up. Railway will happily keep restarting a process that has no money, and each restart re-reads the config for nothing.
Two things worth knowing before this is the only copy running.
MASTER_SECRET is not rotatable. Commitments already on chain were derived
from it. Change it and those packs cannot be revealed by anyone, including you.
Only run one. Two keepers on the same contract both try to reveal the same pack; the loser's transaction reverts, which is harmless but wastes gas, and both queue seeds nobody needs. Stop the local one when Railway takes over.
A single long-running process with no persistence. Every secret is derived from
MASTER_SECRET and its index, so nothing has to survive a restart except that
one value. State that looks like it should be stored - which pack is next, which
seeds are queued - is read off the chain on every pass, because the chain is
already the authoritative copy and a second one would only be a way to disagree
with it.
MIT. See LICENSE.