npm install @shellr/sdk viemTwo things live in here and they are not equally interesting.
Reads and calldata are thin wrappers over viem. They save you an ABI and an
address and nothing else. Writes come back as simulateContract arguments; this
package never holds a key and never sends a transaction.
The draw is a mirror of the contract's randomness, so a settled pack can be recomputed from public data by anyone. That is the part worth depending on.
The whole point. A pack's outcome is decided by a secret the operator committed to before the pack was bought, mixed with a seed the buyer chose. After the reveal, the secret is public - so anybody can check that the coins which arrived are the coins the commitment implied.
import { createPublicClient, http } from "viem";
import { asAddress, robinhoodChain, readDrawConfig, readPack, verifyPack } from "@shellr/sdk";
const client = createPublicClient({ chain: robinhoodChain, transport: http() });
// Addresses are yours. Nothing is baked into this package - see below.
const packs = asAddress(process.env.SHELLR_PACKS_ADDRESS, "ShellrPacks");
// Fetch once and hold it. The line-up changes when the owner adds a coin,
// which is measured in weeks.
const config = await readDrawConfig(client, packs);
const pack = await readPack(client, 1337n, packs);
const { ok, reason, drawn } = verifyPack(pack, config);
if (!ok) console.error(reason);
else {
console.log(`multiplier ${drawn.payout / 100}%`);
console.log(`${drawn.count} coins, ${drawn.spend} wei spent`);
for (const { coin, amountIn } of drawn.draws) {
console.log(coin.token, amountIn);
}
}verifyPack asks two questions in order, because the second is meaningless
without the first.
- Does the revealed secret hash to the commitment the pack took at purchase? The commitment was on chain before the buy, so a secret that matches it is a secret the operator could not have chosen after seeing the purchase.
- Does the draw, recomputed from that secret, match what the contract paid?
A pack that fails the first check is evidence of a broken contract or a broken keeper, and we want to hear about it. A pack that fails the second having passed the first almost always means this package's mirror of the draw has drifted from the deployed contract - check versions before accusing anyone of anything, and open a fairness dispute either way.
import { createWalletClient, custom, parseEther } from "viem";
import { robinhoodChain, buyPack, TIERS } from "@shellr/sdk";
const wallet = createWalletClient({
chain: robinhoodChain,
transport: custom(window.ethereum),
});
// Your seed. Make it random and make it yours - it is half of what stops the
// operator steering your pack. crypto.getRandomValues, not Date.now().
const clientSeed = `0x${[...crypto.getRandomValues(new Uint8Array(32))]
.map((b) => b.toString(16).padStart(2, "0"))
.join("")}` as `0x${string}`;
const rare = TIERS.find((t) => t.key === "rare")!;
const { request } = await client.simulateContract({
...buyPack(clientSeed, rare.price, packs),
account,
});
const hash = await wallet.writeContract(request);Then wait. The keeper reveals it within a pass or two. If it does not, anyone -
not only the buyer - can call refundPack(packId, packs) once revealWindow has
passed. A refund only the buyer can trigger is a refund that never happens for
the buyer who closed the tab.
buyPackWithToken is the $SHELLR path, at a 30% discount. Approve the packs
contract for at least tokenCost(stakeWei) first; the token launched on Pons
and has no permit.
import { expectedPayoutBps, readDrawConfig } from "@shellr/sdk";
const config = await readDrawConfig(client, packs);
expectedPayoutBps(config); // the mean the live bands imply, in bpsSeven payout bands, each with its own chance and its own ends, all of them public state on the contract. The mean of them is the house's side of the deal.
No number is printed here on purpose. setPayoutBands can move them, and a
figure written into a README is a figure that keeps being quoted after it stops
being true. Read the config and compute it.
Every function takes the contract address explicitly, and there is no default to fall back on. That will annoy you once and then never again: a default baked into a released package outlives the redeploy that invalidates it, and the failure is silent - reads succeed against the old contract and return numbers that look entirely plausible. A wrong address should be one you chose.
Chain
robinhoodChain |
viem chain definition. Not in viem/chains, hence here |
ShellrAddresses |
A shape for your own address configuration |
asAddress |
An environment string into an address, or a throw |
explorerTx / explorerAddress |
Explorer links, built from the chain definition |
Packs
readDrawConfig(client, address) |
The line-up, the bands, the fee, the slice |
readPack(client, packId, address) |
One pack as the contract stores it |
verifyPack(pack, config) |
The check above |
buyPack / buyPackWithToken / refundPack |
simulateContract arguments |
TIERS |
The five fixed tiers and their prices |
The draw - pure functions, no client needed
secretFor(master, i) |
Operator-side. Here because verifiers need the derivation |
commitmentFor(secret) |
What goes on chain ahead of demand |
seedFor(secret, clientSeed, packId, buyer) |
The pack's seed |
payoutFor(seed, config) |
The multiplier, in bps of the stake |
countFor(seed, stake, config) |
How many coins drop |
pickFor(seed, i, config) |
Which coin the i-th draw lands on |
drawPack({ … }) |
All of the above, as one pack |
expectedPayoutBps(config) |
The mean the bands imply |
Stock packs
Only stockPacksAbi, and deliberately. Ticker resolution, routing and fill
verification all depend on Voxelithic's registry, and they live in
@shellr/stock-packs
rather than being dragged in here.
Staking
readStakePosition, approveStake, stake, withdrawStake, claimRewards.
Rewards are WETH forwarded from the token's Pons creator fees, not emissions.
There is no honest APR to quote - rewardRate over a quiet week is a lie about a
busy one - so the module returns totalStaked and earned and leaves the
annualising to you.
src/draw.ts is a line-for-line mirror of ShellrPacks._seed, _payout,
_count and _pick. It exists so the fairness claim is runnable rather than
assertable.
Being a mirror also makes it a liability. If the contract's draw changes and
this file does not, verifyPack starts calling honest packs fraudulent. The
test suite pins both against fixed vectors taken from a live pack, and CI fails
on drift.
Do not "fix" a failing vector. Find out which side moved. The same mirror exists a third time in shellr-keeper, where a drift means every reveal reverts on mainnet.
npm test # 25 tests, including 200k draws against the published band odds
npm run buildMIT. See LICENSE.