Developers

StonksPad is a token launchpad. Every StonksPad token on Elysium trades on chain: on its bonding curve until it graduates, then in its StonksSwap V2 pool. This page shows how to read a token, trade it and bridge HYPE from your own site, with short viem examples. No API key is needed.

Network

SettingValue
NetworkElysium, an Arbitrum Orbit network that settles to HyperEVM
Chain ID99901
CurrencyHYPE, 18 decimals (also the gas token)
RPChttps://stonkspad.sh/rpc/elysium, until Elysium's official public RPC launches
Explorerhttps://elysium.kinetiq.xyz/explorer
Parent chainHyperEVM, chain ID 999
import { createPublicClient, createWalletClient, custom, defineChain, http } from "viem";

export const elysium = defineChain({
  id: 99901,
  name: "Elysium",
  nativeCurrency: { name: "HYPE", symbol: "HYPE", decimals: 18 },
  rpcUrls: { default: { http: ["https://stonkspad.sh/rpc/elysium"] } },
});

export const client = createPublicClient({ chain: elysium, transport: http() });
export const wallet = createWalletClient({ chain: elysium, transport: custom(window.ethereum!) });
await wallet.addChain({ chain: elysium }); // once, if the wallet does not know Elysium yet

What our RPC relays

Reads are open to everyone: blocks, balances, eth_call, eth_estimateGas, receipts and eth_getLogs. Each IP has a budget of 600 points a minute: most calls cost 1, eth_call and eth_estimateGas cost 2, eth_getProof 10 and eth_getLogs 20. A log query covers at most 10,000 blocks and 20 addresses, and at most 500 blocks without an address. There are no filters or subscriptions: poll eth_getLogs.

Transactions sent with eth_sendRawTransaction are queued and relayed through the Inbox on HyperEVM, so one is usually included about 15 to 20 seconds after it is sent, not in the next block. We pay the HyperEVM gas of every relay, so the RPC relays StonksPad transactions only:

  • calls to the launcher, the bonding curve, the router, StonksSwap's WHYPE and the other StonksPad contracts that users call (up to 1 KB of calldata);
  • approve and transfer on a token launched by StonksPad;
  • sellWithPermit on the sell helper;
  • claims on a token's vault, dividend tracker, payout and stakers drop contracts;
  • send(to) on the HYPE forwarder, the way a plain HYPE transfer is relayed (it takes a small relay fee);
  • withdrawEth on ArbSys, from 0.0001 HYPE;
  • an empty transaction to your own address, to replace one that is stuck.

Everything else is refused with a message: contract creation, a plain HYPE transfer, and calls to any other contract, including Elysium's canonical WHYPE (0xbA876237B0F021A6c6D852d6c26E107997cC5E81). Until StonksPad on Elysium opens, the RPC relays transactions only from StonksPad's own wallets; reads already work for everyone.

Two rules for every transaction: maxFeePerGas must be at least 115% of the latest base fee (the transaction waits in the queue before it is sequenced, and the base fee can move), and the gas limit should come from eth_estimateGas on this RPC, which includes Elysium's charge for posting the transaction to HyperEVM (at most 30,000,000). viem's defaults meet both.

Per-wallet relay limits

A wallet's limit is what its relays may cost us on HyperEVM in a rolling day. The wallet still pays its own Elysium gas.

WalletRelays may cost a dayAbout, at today's relay cost
New: its first relayed transaction was less than 24 hours ago0.0015 HYPE125 transactions
Recent: less than 7 days0.003 HYPE250 transactions
Established: older than 7 days, and active on at least 3 different days0.01 HYPE850 transactions

Every wallet also has at most 5 transactions waiting at once (2 for a new wallet) and 60 an hour, and each IP at most 200 an hour and 30 a minute. A transaction still waiting after 30 minutes is dropped. All public relays share one daily budget: when it is used up, transactions are refused until it frees up again, and reads go on.

Contracts and ABI files

ContractAddressABI
Bonding curve0x03a35E2C27c1367fcD24F4177E72C373E2902a53curve.json
StonksSwap V2 router0xCF34fB7BcAf43f26f47E729Df51606D3568D9321router.json
Launcher0x07D544d096e2bf730920d1c9F97faF6F2b144f33launcher.json
Token (every StonksPad token)ends in 5555token.json
WHYPE of StonksSwap0xF4781827552C97329371fEB14114e72064b6D444standard WETH9

The ABI files hold the public functions and events an integrator needs, taken from the build of the deployed contracts. In TypeScript, paste each one into your code as export const curveAbi = [ ... ] as const; so viem can type the results. Every other address is on the Contracts page.

The examples below use these constants:

const CURVE = "0x03a35E2C27c1367fcD24F4177E72C373E2902a53";
const ROUTER = "0xCF34fB7BcAf43f26f47E729Df51606D3568D9321";
const LAUNCHER = "0x07D544d096e2bf730920d1c9F97faF6F2b144f33";
const WHYPE = "0xF4781827552C97329371fEB14114e72064b6D444";
const token = "0x...5555"; // the token you trade

Finding tokens

const ours = await client.readContract({ address: LAUNCHER, abi: launcherAbi, functionName: "isToken", args: [token] });
const count = await client.readContract({ address: LAUNCHER, abi: launcherAbi, functionName: "tokenCount" });
const page = await client.readContract({ address: LAUNCHER, abi: launcherAbi, functionName: "getTokens", args: [0n, 100n] });

Every launch emits ElysiumTokenCreated on the launcher, with the token, its pair, its buy and sell tax and the end of its launch protection.

Reading a token's state

import { formatEther, parseEther } from "viem";

const launch = await client.readContract({ address: CURVE, abi: curveAbi, functionName: "getLaunch", args: [token] });
launch.graduated;        // false: trade on the curve. true: trade in the pool, through the router
launch.buyTaxBps;        // buy tax in basis points: 300 is 3%
launch.sellTaxBps;       // sell tax in basis points
launch.protectionEndsAt; // unix seconds: launch protection applies to curve buys before this time
launch.pair;             // the token's StonksSwap V2 pool

const [, , , tokensLeft, price, progressBps] = await client.readContract({ address: CURVE, abi: curveAbi, functionName: "state", args: [token] });
formatEther(price);      // HYPE per token on the curve
Number(progressBps) / 100; // percent of the curve's tokens sold

const { timestamp } = await client.getBlock();
const protectedNow = timestamp < launch.protectionEndsAt; // compare with block time, not your own clock

Quotes on the curve already take off the 1% curve fee and the token's tax:

const buyQuote = await client.readContract({ address: CURVE, abi: curveAbi, functionName: "quoteBuy", args: [token, parseEther("1")] });
buyQuote.tokens;  // tokens you get for 1 HYPE
buyQuote.refund;  // HYPE sent back when this buy takes the last tokens of the curve

const sellQuote = await client.readContract({ address: CURVE, abi: curveAbi, functionName: "quoteSell", args: [token, parseEther("1000000")] });
sellQuote.net;    // HYPE you get for 1,000,000 tokens

After graduation the price comes from the pool. The router's quote does not include the token's tax: take it off yourself, as in the router examples below.

const [, hypeOut] = await client.readContract({ address: ROUTER, abi: routerAbi, functionName: "getAmountsOut", args: [parseEther("1"), [token, WHYPE]] });

Buying and selling on the curve

Before graduation every trade goes through the bonding curve:

function buy(address token, uint256 minTokensOut, address to, uint256 deadline) payable returns (uint256 tokensOut)
function buyExactTokens(address token, uint256 tokensOut, address to, uint256 deadline) payable returns (uint256 hypeIn)
function sell(address token, uint256 tokensIn, uint256 minHypeOut, address to, uint256 deadline) returns (uint256 hypeOut)

minTokensOut and minHypeOut are your slippage limit: take them from a fresh quote, less the slippage you accept. deadline is a unix time in seconds; give it a few minutes, because a transaction waits in the relay queue before it is included. buy spends exactly the HYPE you send (value), and if the curve runs out of tokens the rest comes back. buyExactTokens takes value as the most you pay and refunds the rest.

const [account] = await wallet.requestAddresses();
const slippageBps = 300n; // 3%
const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); // 10 minutes

// buy with 0.5 HYPE
const hypeIn = parseEther("0.5");
const q = await client.readContract({ address: CURVE, abi: curveAbi, functionName: "quoteBuy", args: [token, hypeIn] });
const minTokensOut = (q.tokens * (10_000n - slippageBps)) / 10_000n;
await wallet.writeContract({ account, address: CURVE, abi: curveAbi, functionName: "buy", args: [token, minTokensOut, account, deadline], value: hypeIn });

To sell, the curve takes your tokens with transferFrom, so it needs an allowance first. Wait for the approval to be included before you send the sell, or the sell's gas estimate fails.

const tokensIn = parseEther("1000000");
const approval = await wallet.writeContract({ account, address: token, abi: tokenAbi, functionName: "approve", args: [CURVE, tokensIn] });
await client.waitForTransactionReceipt({ hash: approval });

const s = await client.readContract({ address: CURVE, abi: curveAbi, functionName: "quoteSell", args: [token, tokensIn] });
const minHypeOut = (s.net * (10_000n - slippageBps)) / 10_000n;
await wallet.writeContract({ account, address: CURVE, abi: curveAbi, functionName: "sell", args: [token, tokensIn, minHypeOut, account, deadline] });

Sells are never restricted or paused. Buys follow the launch protection rules below while it lasts. A token's Trade events on the curve carry every trade, and Graduated marks the move to its pool.

After graduation: the StonksSwap V2 router

Once graduated is true the curve refuses trades ("Curve is closed") and the token trades in its pool. Use the router at 0xCF34fB7BcAf43f26f47E729Df51606D3568D9321 with these two functions; the plain swap functions do not work with the token's tax:

function swapExactETHForTokensSupportingFeeOnTransferTokens(uint256 amountOutMin, address[] path, address to, uint256 deadline) payable
function swapExactTokensForETHSupportingFeeOnTransferTokens(uint256 amountIn, uint256 amountOutMin, address[] path, address to, uint256 deadline)

The path is [WHYPE, token] to buy and [token, WHYPE] to sell, with StonksSwap's WHYPE 0xF4781827552C97329371fEB14114e72064b6D444 (the router's WETH()). The token takes its tax in tokens on every transfer from or to its pool, so adjust the router's quote before you apply slippage. The pool fee (0.30%) is already in the quote.

// buy: the buy tax comes off the tokens the pool sends you
const hypeIn = parseEther("0.5");
const [, out] = await client.readContract({ address: ROUTER, abi: routerAbi, functionName: "getAmountsOut", args: [hypeIn, [WHYPE, token]] });
const afterTax = (out * (10_000n - BigInt(launch.buyTaxBps))) / 10_000n;
const amountOutMin = (afterTax * (10_000n - slippageBps)) / 10_000n;
await wallet.writeContract({ account, address: ROUTER, abi: routerAbi, functionName: "swapExactETHForTokensSupportingFeeOnTransferTokens", args: [amountOutMin, [WHYPE, token], account, deadline], value: hypeIn });

// sell: approve the router, then quote what reaches the pool after the sell tax
const amountIn = parseEther("1000000");
const ok = await wallet.writeContract({ account, address: token, abi: tokenAbi, functionName: "approve", args: [ROUTER, amountIn] });
await client.waitForTransactionReceipt({ hash: ok });
const reaching = (amountIn * (10_000n - BigInt(launch.sellTaxBps))) / 10_000n;
const [, hype] = await client.readContract({ address: ROUTER, abi: routerAbi, functionName: "getAmountsOut", args: [reaching, [token, WHYPE]] });
const minHype = (hype * (10_000n - slippageBps)) / 10_000n;
await wallet.writeContract({ account, address: ROUTER, abi: routerAbi, functionName: "swapExactTokensForETHSupportingFeeOnTransferTokens", args: [amountIn, minHype, [token, WHYPE], account, deadline] });

Swaps in the pool are standard V2 Swap events on the pair. The token's own TaxCollected event shows the tax of each one.

Launch protection

A creator can protect the first minutes of a launch: up to 30 minutes, until protectionEndsAt. During that window every curve buy must follow these rules, and a buy that breaks one reverts with the message in brackets:

  • Per-wallet cap: at most 20,000,000 tokens (2% of the supply) per wallet over the whole window ("Launch protection: wallet cap").
  • Cooldown: at least 30 seconds between two buys of the same wallet ("Launch protection: cooldown").
  • No contracts: the buyer must be the wallet that signs the transaction, so contracts, routers and aggregators cannot buy ("Launch protection: contracts cannot buy").
  • For yourself only: to must be the buyer's own address ("Launch protection: buy for yourself").
  • Low tip: the priority fee must be at most the curve's maxProtectedPriorityFee(), currently 1 gwei ("Launch protection: priority fee").

Sells are never restricted. To see where a wallet stands, read boughtInProtection(token, wallet) and lastProtectedBuyAt(token, wallet) on the curve.

Bridging HYPE from HyperEVM

HYPE comes to Elysium through the Inbox on HyperEVM (chain ID 999) at 0xce59f8B65ad41C9DAE4c07A0571a39824bD92cff. Call depositEth() with the amount as value: the HYPE arrives at the same address on Elysium, usually in about 15 seconds. This transaction is on HyperEVM and pays HyperEVM gas; it does not go through our RPC.

import { defineChain, parseAbi, parseEther } from "viem";

const hyperEvm = defineChain({
  id: 999,
  name: "HyperEVM",
  nativeCurrency: { name: "HYPE", symbol: "HYPE", decimals: 18 },
  rpcUrls: { default: { http: ["https://rpc.hyperliquid.xyz/evm"] } },
});
const inboxAbi = parseAbi(["function depositEth() payable returns (uint256)"]);

const parentWallet = createWalletClient({ chain: hyperEvm, transport: custom(window.ethereum!) });
await parentWallet.switchChain({ id: 999 });
await parentWallet.writeContract({ account, address: "0xce59f8B65ad41C9DAE4c07A0571a39824bD92cff", abi: inboxAbi, functionName: "depositEth", value: parseEther("0.1") });

Deposit only from a regular wallet. A contract wallet (a Safe or another smart account) is credited at a different, aliased address on Elysium, which it may not control.

Withdrawals take about 6 days. On Elysium, call withdrawEth(destination) on ArbSys (0x0000000000000000000000000000000000000064) with the amount as value. After the challenge period, finish it on HyperEVM with executeTransaction on the Outbox (0xeC6B106DfA14428FE5Eec2a4065a7be599f02728), using the proof an Elysium node returns from constructOutboxProof on NodeInterface (0x00000000000000000000000000000000000000C8). The StonksPad Bridge & Transfer page coming soon does both steps for its users.

Common mistakes

  • The wrong WHYPE. StonksSwap pools pair each token with 0xF4781827552C97329371fEB14114e72064b6D444, the router's WETH(). Elysium's canonical WHYPE (0xbA876237B0F021A6c6D852d6c26E107997cC5E81) is a different token: a path with it fails, and our RPC refuses calls to it.
  • The wrong venue. The curve refuses trades after graduation ("Curve is closed"), and before graduation the token refuses transfers to its pool ("Pool transfers are blocked until graduation"). Read graduated first.
  • Plain router swaps. Use the SupportingFeeOnTransferTokens functions. Without them a sell reverts, and a buy checks your minimum before the tax comes off.
  • Slippage without the tax. In the pool, take the buy or sell tax off the router's quote before you apply slippage. Curve quotes already include it.
  • No approval. A sell needs an allowance for the contract that takes the tokens: the curve before graduation, the router after it. Wait for the approval to be included before you send the sell.
  • A deadline of seconds. Transactions through our RPC wait in a queue first. Give the deadline a few minutes.
  • Fees. maxFeePerGas at the exact base fee is refused (at least 115%). A priority fee above 1 gwei makes a curve buy revert during launch protection.
  • Buying through a contract, or for someone else, during launch protection. Both revert until the window ends.
  • Your own clock. Compare protectionEndsAt and deadlines with the latest block's timestamp.
  • Basis points. buyTaxBps and sellTaxBps are basis points: 300 is 3%.
  • Plain HYPE transfers through our RPC. They are refused. Use the HYPE forwarder's send(to) or the Bridge & Transfer page coming soon.
  • Bridging from a contract wallet. The HYPE lands at an aliased address on Elysium.