Overview
Greenwood is a meme-token launchpad on Robinhood Chain, an Arbitrum Orbit L2 where the gas token is ETH. One transaction (GreenwoodLaunchpad.launch()) deploys a fixed-supply ERC-20, opens a TOKEN / ETH Uniswap v4 pool with the Greenwood hook, puts 100 % of the supply in it and mints the LP position to 0x…dEaD. Nobody can ever pull the liquidity.
There is no configurable tax: every swap pays a fixed 1 % hook fee. FEE_BPS = 100 is a constant and the hook has no owner, so the rate can never change. The fee is split 80 % to the launch’s fees recipient (the creator by default) and 20 % to the protocol.
One-click launch
Deploy, v4 pool, burned LP and an optional first buy, in one atomic transaction.
Liquidity can’t be pulled
The LP position is minted to 0x…dEaD at launch: nobody, creator or Greenwood, can withdraw it. It does not cap the creator’s first buy.
Fixed 1 % fee
A constant in the hook, the same on buys and sells. It never changes.
80 % to creators
On every swap, for the life of the pool, paid in the token and in ETH.
Plain ERC-20
No tax, no mint, no proxy. Ownership is renounced in the constructor.
Live on Uniswap from block one
No bonding curve, no graduation: the token trades on its final v4 pool right away.
$WOOD is Greenwood’s official platform token, pinned on the board.
Robinhood Chain
Greenwood runs on Robinhood Chain, an EVM-compatible Arbitrum Orbit L2. Any wallet that supports custom networks works, and gas is paid in ETH.
Pools pair against ETH
currency0 = address(0), 18 decimals. It always sorts first, so the token is always currency1. The ERC-20 view of the same funds at 0x3600…0000 (6 decimals) is not used by any pool. If you integrate, never mix the two decimal bases.Quote token: ETH
Every Greenwood token trades against a single quote: ETH. Nothing is chosen at launch, and there are no other quote assets.
msg.value), and the launch’s first buy is msg.value too. Only sells need an approval: the token to Permit2 once, then Permit2 to the UniversalRouter, which the app grants for 30 days.Launching a token
From Create, fill in a name, ticker, description and logo, choose a starting market cap, optionally add a first buy, and sign once. The app calls GreenwoodLaunchpad.launch(LaunchParams) (payable). One atomic transaction does everything:
- 1Deploy a full GreenwoodToken contract (not a clone) via CREATE2. Its constructor mints the whole supply to the launchpad and renounces ownership.
- 2Initialise the Uniswap v4 pool: ETH / token, LP fee 0, tick spacing 200, hooks = LaunchHook, at the start tick of the chosen market cap. A pool that is already initialised makes the launch revert.
- 3Hand the pool’s fees-recipient role in the hook to your
feesRecipient(address(0)= you), before any swap can pay a fee. - 4Mint a single-sided, token-only position over
[minUsableTick, startTick]holding 100 % of the supply, straight to0x…dEaD: burned. - 5Optional first buy with
msg.valuethrough the UniversalRouter, tokens sent to your wallet. - 6Refund the rounding dust, record the
Launchand emitLaunched.
Starting market cap
The starting market cap sets the pool’s opening price. The start tick is the multiple of 200 closest to that price, so the real figure lands within about 1 % of the preset.
// token = currency1, ETH = currency0 (both 18 decimals)
tokensPerUsdc(tick) = 1.0001 ^ tick
startTick = round(log(supply / marketCapUsd) / log(1.0001) / 200) * 200
marketCap(tick) = supply / 1.0001 ^ tick
marketCap(sqrtP) = supply / (sqrtPriceX96 / 2^96)^2The token contract
Every launch deploys the same contract, GreenwoodToken: an OpenZeppelin ERC-20 whose supply is minted once, in its constructor. It is a real contract at the token address, not a proxy: there is no implementation to swap and nothing that can mint.
Why the token address is not predictable
prevrandao is always 0). If a token address could be computed in advance, someone could initialise its v4 pool first, at another price, and make the launch revert. So the CREATE2 salt mixes 32 random bytes from your browser with your address, the block number and a nonce. The address stays unknowable until the transaction is public, and a reverted launch never burns an address: retry with a fresh salt.Fee model
FixedGreenwood has no configurable tax and no allocation buckets. Every pool carries LaunchHook, which takes a fixed 1 % on every swap and books it for the pool. The rate is a constant in the hook’s code and the hook has no owner.
What the hook does
afterSwapbooks the fee
Takes 1 % of the unspecified side from the PoolManager and books it per pool (owed0 ETH, owed1 token). It pays no one during the swap, so no recipient can ever make a swap revert.
collectFeessplits 80 / 20
Anyone can call collectFees(poolKey): 80 % goes to the fees recipient, 20 % to the protocol. The caller can’t redirect anything.
beforeSwapfeeds the TWAP
Folds the pre-swap tick into an on-chain TWAP accumulator, at most once per block, keeping at most one checkpoint per clock minute.
Where the protocol’s 20 % goes
TREASURY. Greenwood’s deploy script sets it to the treasury, the deployer’s address. No part of it is paid to token holders. On the hook linked on this page, TREASURY is 0xfd08…6289.Launch ordering, no snipe window
Greenwood has no anti-snipe machinery. The only thing it guarantees is ordering: the creator’s optional first buy runs inside the launch transaction, right after the pool is created, so nobody can buy before it.
That first buy has no cap. A creator can buy any share of the supply at the launch price, before anyone else, and sell it to later buyers. Burned liquidity does not prevent that: before you buy, look at the launch transaction and the creator’s balance.
Trading
Each token page has a swap panel that routes through Uniswap’s UniversalRouter and quotes with the V4Quoter. The pool is a standard Uniswap v4 pool, so any v4-aware wallet, aggregator or bot can trade it too.
Fees
Fees accumulate per pool in LaunchHook: fees(poolId) returns the recipient and what is owed in ETH (owed0) and in the token (owed1). They are split when LaunchHook.collectFees(poolKey) is called, by anyone: from the token page’s Collect fees button or directly on the contract.
80 % Creator
the launch’s fees recipient
20 % Protocol
the hook’s TREASURY
Example: 1 % hook fee (FEE_BPS = 100), split 80 / 20 on collect
Buy, exact-in: 1 ETH in → the pool outputs X tokens
fee = 1 % of X (in tokens, booked as owed1)
creator (80 %) = 0.8 % of X → fees recipient
protocol (20 %) = 0.2 % of X → TREASURY (token side)
trader receives = 99 % of X
Sell, exact-in: tokens in → the pool outputs 1 ETH
fee = 0.01 ETH (native ETH, booked as owed0)
creator (80 %) = 0.008 ETH → fees recipient
protocol (20 %) = 0.002 ETH → TREASURY (ETH side)
trader receives = 0.99 ETHPayout rules
Creator earnings
Every launch has a fees recipient: your wallet by default, or any address chosen at launch. It earns 80 % of every swap fee on the pool, for as long as the pool exists.
No graduation
NoneEvery token is in its final Uniswap v4 pool from the moment it launches: the LP is burned to 0x…dEaD in the launch transaction. There is no bonding curve and nothing to graduate to.
Contracts & addresses
Robinhood ChainAll Greenwood contracts are immutable: no proxies, no upgrade path. GreenwoodLaunchpad, LaunchHook and every GreenwoodToken have no owner: nobody can pause trading, change the fee or redirect a creator’s 80 %.
GreenwoodLaunchpad
Deploys tokens, creates and seeds pools, burns the LP, launch registry
LaunchHook
Fixed 1 % swap fee, 80 / 20 split, on-chain TWAP oracle
Uniswap v4 PoolManager
Core AMM: every v4 pool lives here
Uniswap v4 PositionManager
LP position NFTs
UniversalRouter
Swaps from the app
V4Quoter
Swap quotes (simulated, hook included)
StateView
Pool price reads (getSlot0)
Permit2
Token allowances for sells
ETH (ERC-20 view)
6 decimals, same funds as ETH
Multicall3
Batched reads
HOOK_FLAGS = 0x10C4). It is deployed with CREATE2 and a mined salt, and its constructor refuses any other address.Who controls what
Previous deployment (retired)
Integration guide
For buildersEverything an explorer, a bot or an aggregator needs is on-chain: no API key, no indexer. The launchpad keeps a registry you can page through, and every pool is a standard Uniswap v4 pool.
1 · Reading token metadata
There is no contractURI() / EIP-7572: the metadata lives in the launch record, as a JSON string of at most 2,048 bytes stored on-chain, logo included.
import { createPublicClient, http } from "viem";
const client = createPublicClient({ transport: http("https://rpc.mainnet.chain.robinhood.com") });
// reverts BadArgs if the token was not launched by this launchpad
const launch = await client.readContract({
address: LAUNCHPAD, abi: launchpadAbi, functionName: "launchOf", args: [token],
});
const meta = JSON.parse(launch.metadata);
// {
// "description": "What this token is about",
// "image": "data:image/webp;base64,UklGR…", // square logo ≤ 96 px, on-chain
// "socials": ["https://x.com/yourtoken"],
// "website": "https://yourtoken.xyz"
// }2 · Discovering all launches
event Launched(
address indexed creator,
address indexed token,
bytes32 poolId,
uint256 nftId, // burned LP position
uint256 supply,
int24 initTick,
string name,
string symbol
); // metadata is not in the event: read launchOf(token)
totalLaunches() → uint256
getLaunches(start, stop) → Launch[] // [start, stop), clamped
launchesByCreator(creator) → address[]
launchIndexByToken(token) → index + 1 // 0 = not a Greenwood launchCheck the pool, not just the token
launchIndexByToken(token) > 0 alone does not prove you have the right pool. Require poolId == launchOf(token).poolId, or equivalently the exact key { currency0: 0x0, currency1: token, fee: 0, tickSpacing: 200, hooks: LaunchHook }.3 · Reading the v4 pool
// PoolKey of every Greenwood launch: ETH always sorts first
const key = {
currency0: "0x0000000000000000000000000000000000000000", // ETH
currency1: token,
fee: 0,
tickSpacing: 200,
hooks: GREENWOOD_HOOK,
};
poolId = keccak256(abi.encode(key)) // also stored in launch.poolId
StateView.getSlot0(poolId) → (sqrtPriceX96, tick, protocolFee, lpFee)
tokensPerUsdc = (sqrtPriceX96 / 2^96)^2 // both sides 18 decimals: no 10^12 factor
priceUsd = 1 / tokensPerUsdc
marketCapUsd = supply / tokensPerUsdc
PositionManager.ownerOf(launch.nftId) == 0x…dEaD // the LP is burned4 · Swapping
Call UniversalRouter.execute(commands, inputs, deadline) with command V4_SWAP (0x10) and the actions SWAP_EXACT_IN_SINGLE, SETTLE_ALL, TAKE_ALL. A buy, as the app sends it:
import { encodeAbiParameters, encodePacked } from "viem";
const actions = encodePacked(["uint8", "uint8", "uint8"], [0x06, 0x0c, 0x0f]);
const params = [
encodeAbiParameters([exactInputSingleParams], [{
poolKey: key, zeroForOne: true, amountIn, amountOutMinimum: minOut, hookData: "0x",
}]),
encodeAbiParameters([{ type: "address" }, { type: "uint256" }], [key.currency0, amountIn]), // SETTLE_ALL
encodeAbiParameters([{ type: "address" }, { type: "uint256" }], [key.currency1, minOut]), // TAKE_ALL
];
const input = encodeAbiParameters([{ type: "bytes" }, { type: "bytes[]" }], [actions, params]);
await walletClient.writeContract({
address: UNIVERSAL_ROUTER, abi: universalRouterAbi, functionName: "execute",
args: ["0x10", [input], deadline],
value: amountIn, // native ETH, 18 decimals: no approval
});Where to find what
On-chain reference
No API keyGreenwood has no public REST API: the app’s own server routes (RPC relay, analytics, source verification) are internal. Everything below is a contract call on Robinhood Chain, readable through any RPC.
Base
rpc https://rpc.mainnet.chain.robinhood.com // Robinhood Chain, chain ID 4663
launchpad 0x5710Fb3E6E9654D220ea3F3e495b7D376B2bD81B
hook 0x7D4963d6ADFD22350e1556c6b43897BcbFaad0c4GreenwoodLaunchpad
launch((name, symbol, metadata, supply, absTick, feesRecipient, salt)) payable → (token, poolId, nftId)totalLaunches() → uint256launchAt(index) → LaunchlaunchOf(token) → LaunchgetLaunches(start, stop) → Launch[]launchesByCreator(creator) → address[]launchIndexByToken(token) → uint256POOL_FEE = 0 · TICK_SPACING = 200 · DEAD · MAX_METADATA_BYTES = 2048 · HOOKSLaunched(creator, token, poolId, nftId, supply, initTick, name, symbol)LaunchHook
fees(poolId) → (recipient, owed0, owed1)collectFees(poolKey) → (amt0, amt1)setFeesRecipient(poolId, to)claimPending()pendingNative(address) → uint256currentTick(poolId) → int24consult(poolId, secondsAgo) → int24observe(poolId, secondsAgos[]) → int64[]latest(poolId) → (timestamp, tickCumulative)checkpointCount(poolId) → uint256getCheckpoints(poolId, start, stop) → Observation[]FEE_BPS = 100 · HOOK_FLAGS = 4292 · CHECKPOINT_INTERVAL = 60 · POOL_MANAGER · TREASURYFeesCollected · FeesRecipientSet · OracleInitialized · PendingCredited · PendingClaimedErrors
BadArgs()BadTick()NotFeesRecipient()NoFees()NothingPending()TooOld() · NotInitialized()Launch record
struct Launch {
address token;
bytes32 poolId; // Uniswap v4 pool id
uint256 nftId; // LP position, owned by 0x…dEaD
address creator;
address feesRecipient; // at launch; live: LaunchHook.fees(poolId).recipient
uint64 timestamp;
int24 initTick; // start price
uint256 supply; // 18 decimals
string name;
string symbol;
string metadata; // JSON: description, image, socials, website
}Price history
getCheckpoints(poolId, start, stop) → Observation[] // ≤ 1 per clock minute, the pool's whole life
struct Observation { uint48 timestamp; int64 tickCumulative; }
// exact TWAP between two consecutive checkpoints a and b:
meanTick = (b.tickCumulative - a.tickCumulative) / (b.timestamp - a.timestamp)
tokensPerUsdc = 1.0001 ^ meanTick
priceUsd = 1 / tokensPerUsdc
// live end of the series: observe(poolId, [0])0x3600…0000) uses 6.