Skip to content

Repository files navigation

@shellr/sdk

Read Shellr's contracts. Recompute a pack. Check that it was fair.

npm Types Tests Bundle

npm install @shellr/sdk viem

Two 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.


Verify a pack

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.

  1. 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.
  2. 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.


Buy a pack

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.


The odds, computed rather than quoted

import { expectedPayoutBps, readDrawConfig } from "@shellr/sdk";

const config = await readDrawConfig(client, packs);
expectedPayoutBps(config); // the mean the live bands imply, in bps

Seven 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.


API

Addresses are not shipped

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.


A note on the mirror

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 build

License

MIT. See LICENSE.

About

Read Shellr's contracts and verify that a pack was drawn fairly.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages