Pons Launch — launchToken on Robinhood Chain
Product truth (do not dilute)
- Pons launches fixed-supply ERC-20s (
PonsLauncherToken) with a Uniswap V3 pool, not a bonding curve. - Factory entry point is
launchToken(selector0x686399cb), not a guessedcreateLaunch. - Supply / max-wallet / max-tx / restriction blocks come from on-chain LaunchConfig (
launchConfigId), not free-form agent inventing. - Launch costs
launchFeeETH (live-read; historically 0.0005 ETH) plus gas;msg.valueabove the fee funds optional atomic initial buy. - Cheshire does not own Pons bytecode; we are a partner surface (like NOXA): registry + prepare + wallet sign + verify.
Canonical addresses (Robinhood mainnet 4663)
Grounded in shipped client/src/lib/pons-launch/registry.ts and live probes.
| Item | Value |
|---|---|
| PonsLaunchFactory (current) | 0xA5aAb3F0c6EeadF30Ef1D3Eb997108E976351feB |
| Legacy factory ($PONS only) | 0x0c37a24F5D23A486FA692d1500881d698B1F77a4 |
| Pair token (WETH) | 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73 |
| DEX V3 factory | 0x1f7d7550B1b028f7571E69A784071F0205FD2EfA |
| NonfungiblePositionManager | 0x73991a25C818Bf1f1128dEAaB1492D45638DE0D3 (PONS_DEFAULT_POSITION_MANAGER) |
| Swap router (dex config 0) | 0xCaf681a66D020601342297493863E78C959E5cb2 (PONS_DEFAULT_SWAP_ROUTER — from factory getDexConfig(0)) |
| Default pool fee | 10000 (1%) (PONS_DEFAULT_POOL_FEE) |
| Default launchConfigId / dexId | 0 / 0 (read live before relying) |
| Chain ID | 4663 |
| Public RPC | https://rpc.mainnet.chain.robinhood.com (rate-limited) |
| App RPC | Set RH_RPC_URL (Alchemy/QuickNode preferred) |
| Explorer | https://robinhoodchain.blockscout.com |
| Human UI (Cheshire) | /pons-launch (launch) · /pons-launch/{token} (trade) |
| Demo trade token | 0x41e6318d44E0290CDd8c77219B57D17E44Bb59aB (CTESTV2IW) |
| Status API | GET /api/pons/launch/status |
| Prepare API | POST /api/pons/launch/prepare |
| Shipped helpers | client/src/lib/pons-launch/* (launch + V3 trade) |
Factory may migrate. Re-discover (below) if launches fail or status drifts.
When to use which path
| Goal | Action |
|---|---|
| User wants click-launch in browser | Send to /pons → Wallet launch (EVM wallet on 4663) |
| Agent has funded RH EVM key + RPC | preparePonsLaunch → simulate → launchToken → verify receipt |
| User wants bonding-curve / pump-style RH | Use rh-bonded-launch (createToken on 0x3f60…) |
| User wants Solana $CLAWD | Stop — wrong chain/product |
| User only wants Pons market data | Firecrawl monitor: GET /api/pons/snapshot (not a launch) |
Factory reverse-discovery (no key from Pons required)
Every PonsLauncherToken stores:
address public immutable launchFactory; // set to msg.sender in constructor
Recipe
- Collect graduated/live token addresses from
https://pons.family/launchpadorGET /api/pons/launchpad. - For each token,
eth_calllaunchFactory(). - Vote: most common non-zero address = current factory.
- Confirm with factory
getLaunchedToken(token)→exists == true. - Optionally read token
dexFactory(),positionManager(),pairToken(),poolFee()to refresh periphery defaults.
// Pseudocode — use viem readContract
const factory = await read(token, "launchFactory()");
const row = await read(factory, "getLaunchedToken(token)");
// row.exists === true for tokens of that factory
Do not invent a factory address. Prefer shipped registry defaults, then re-probe if needed.
Pre-flight (always)
import { createPublicClient, http, formatEther } from "viem";
const FACTORY = "0xA5aAb3F0c6EeadF30Ef1D3Eb997108E976351feB" as const;
// launchEnabled() → true
// launchFee() → wei (e.g. 5e14 = 0.0005 ETH)
// getLaunchConfig(0) → enabled, pairToken, supply, maxWalletBps, maxTxBps, restrictionBlocks
// getDexConfig(0) → enabled, factory, positionManager, poolFee
// wallet balance ≥ launchFee + gas buffer (~0.0003–0.002 ETH depending on gas)
// chainId === 4663
Live snapshot (verify; do not hardcode forever):
| Field | Observed |
|---|---|
| launchEnabled | true |
| launchFee | 0.0005 ETH |
| LaunchConfig 0 supply | 1e9 * 1e18 |
| maxWalletBps / maxTxBps | 500 / 550 |
| restrictionBlocks | 366 |
| Dex 0 poolFee | 10000 |
Agent workflow (browser / human-in-the-loop)
- Confirm product: Pons V3 launch, not bonding curve, not $CLAWD.
- Open
/pons(Cheshire) → Wallet launch (Pons V3). - Connect EVM wallet → Robinhood Chain (4663) with ETH.
- Name + symbol (+ optional description / extra buy above fee).
- User confirms tx (pays launchFee + optional buy).
- Report token, pool, explorer tx, and
https://pons.family/launchpad/{token}.
Agent workflow (programmatic — prefer shipped helpers)
Source of truth (import these; do not reimplement ABI from memory):
preparePonsLaunch—client/src/lib/pons-launch/prepare.tsverifyPonsLaunchReceipt—client/src/lib/pons-launch/receipt.tsgetPonsLaunchRegistry—client/src/lib/pons-launch/registry.ts- API:
POST /api/pons/launch/preparewith{ name, symbol, feeWallet, launchConfigId, dexId, valueWei }
Encode + send (viem)
import {
createPublicClient,
createWalletClient,
http,
defineChain,
type Hex,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { preparePonsLaunch } from "./client/src/lib/pons-launch/prepare.ts";
import { verifyPonsLaunchReceipt } from "./client/src/lib/pons-launch/receipt.ts";
const RH = defineChain({
id: 4663,
name: "Robinhood Chain",
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
rpcUrls: {
default: { http: [process.env.RH_RPC_URL || "https://rpc.mainnet.chain.robinhood.com"] },
},
blockExplorers: {
default: { name: "Blockscout", url: "https://robinhoodchain.blockscout.com" },
},
});
/**
* Launch a Pons token. Never log or commit private keys.
* Prefer user-signed wallet over agent-held keys.
* Require explicit user confirmation before broadcast.
*/
export async function launchPonsToken(opts: {
privateKey?: `0x${string}`; // only with explicit user approval
name: string;
symbol: string;
description?: string;
logo?: string;
/** Defaults to the signing address */
feeWallet?: `0x${string}`;
launchConfigId?: number;
dexId?: number;
/** Total msg.value = launchFee + optional initial buy */
valueWei?: bigint;
}) {
if (!opts.privateKey) throw new Error("Need user wallet or authorized RH_EVM_PRIVATE_KEY");
const account = privateKeyToAccount(opts.privateKey);
const publicClient = createPublicClient({ chain: RH, transport: http() });
const walletClient = createWalletClient({
account,
chain: RH,
transport: http(),
});
const FACTORY = "0xA5aAb3F0c6EeadF30Ef1D3Eb997108E976351feB" as const;
const fee = await publicClient.readContract({
address: FACTORY,
abi: [{ type: "function", name: "launchFee", stateMutability: "view", inputs: [], outputs: [{ type: "uint256" }] }] as const,
functionName: "launchFee",
});
const prepared = preparePonsLaunch({
name: opts.name,
symbol: opts.symbol,
description: opts.description,
logo: opts.logo,
feeWallet: opts.feeWallet ?? account.address,
launchConfigId: opts.launchConfigId ?? 0,
dexId: opts.dexId ?? 0,
valueWei: opts.valueWei ?? fee,
});
// selector must be launchToken
if (!prepared.data.startsWith("0x686399cb")) {
throw new Error(`Unexpected selector ${prepared.data.slice(0, 10)} — expected launchToken 0x686399cb`);
}
await publicClient.call({
account: account.address,
to: prepared.to,
data: prepared.data,
value: prepared.value,
});
const hash = await walletClient.sendTransaction({
account,
chain: RH,
to: prepared.to,
data: prepared.data as Hex,
value: prepared.value,
});
const receipt = await publicClient.waitForTransactionReceipt({ hash });
const verified = verifyPonsLaunchReceipt({
launchFactory: prepared.to,
deployer: account.address,
receipt: {
status: receipt.status,
logs: receipt.logs.map((l) => ({
address: l.address,
data: l.data,
topics: l.topics,
})),
},
});
if (!verified.ok) throw new Error(verified.reason);
return {
txHash: hash,
token: verified.token,
pool: verified.pool,
explorerTx: `https://robinhoodchain.blockscout.com/tx/${hash}`,
explorerToken: `https://robinhoodchain.blockscout.com/token/${verified.token}`,
ponsUrl: `https://pons.family/launchpad/${verified.token}`,
};
}
HTTP prepare (Cheshire server)
curl -sS -X POST "$ORIGIN/api/pons/launch/prepare" \
-H 'content-type: application/json' \
-d '{
"name":"Cheshire Test",
"symbol":"CTEST",
"feeWallet":"0xYourEvmAddress",
"launchConfigId":0,
"dexId":0,
"valueWei":"500000000000000"
}'
# → { to, data, value, salt, … } then wallet_sendTransaction
Proven test launch (regression reference)
- Tx:
0xe5a35a584e8ef9c09e9cd4d1953ad7d39a341c8854c024ccb36a7208a751b86c - Token:
0x41e6318d44E0290CDd8c77219B57D17E44Bb59aB - Pool:
0xc686755CECea371A99fd9CB110EDFad8af0056B1
On-site trading (host the market — not RH bonding)
Pons tokens are already graduated into a locked Uniswap V3 pool at launch. There is no bonding-curve buy/sell. “Host bonding” on Cheshire = host the V3 market via SwapRouter exactInputSingle.
Trade surfaces
| Route | Page |
|---|---|
/pons-launch/{token} |
PonsTokenPage + PonsTradePanel |
/pons/token/{token} |
same |
/launch/pons/{token} |
same |
| Demo | /pons-launch/0x41e6318d44E0290CDd8c77219B57D17E44Bb59aB |
Trade path (source of truth)
getLaunchedToken(token)on factory →exists,pairedToken,poolFee,isToken0,restrictionsEndBlock,supply- V3
dexFactory.getPool(token, pairedToken, poolFee)→ pool pool.slot0()→sqrtPriceX96for spot estimate- Encode SwapRouter02
exactInputSingle(0x04e45aaf, not classic0x414bf389) to swap router0xCaf681…- Buy:
tokenIn=WETH,tokenOut=token,msg.value=amountIn - Sell: approve token → router, then
tokenIn=token,tokenOut=WETH,value=0 - Classic deadline-in-struct selector reverts on this router (verified via estimateGas)
- Buy:
- Precheck restriction window via
preparePonsTrade/assertRestrictedPoolBuyAllowed
Helpers:
preparePonsExactInputSingle—client/src/lib/pons-launch/swap.tspreparePonsTrade/PONS_DEMO_TOKEN—client/src/lib/pons-launch/trade.ts- UI —
client/src/components/PonsTradePanel.tsx
Proven demo market (Cheshire Test V2IW)
| Field | Value |
|---|---|
| Token | 0x41e6318d44E0290CDd8c77219B57D17E44Bb59aB |
| Symbol | CTV2IW |
| Pool | 0xc686755CECea371A99fd9CB110EDFad8af0056B1 |
| Fee | 10000 (1%) |
| isToken0 | false (WETH is token0) |
| Pair | WETH 0x0Bd7…AD73 |
| Router | 0xCaf681a66D020601342297493863E78C959E5cb2 |
Restriction education (PonsLauncherToken)
Optional pre-trade education (pure helpers: client/src/lib/pons-launch/policy.ts):
- Launch-block pool buys blocked except factory atomic initial-buy exemption.
- During restriction window: max wallet BPS of supply; cumulative max-tx BPS of supply.
- After
restrictionEndBlock: unrestricted ERC-20 transfers.
Agents do not set these at launch time when using launchToken — they are fixed by LaunchConfig.
Safety rules for agents
- Never print, log, or commit private keys / seed phrases.
- Prefer user-signed wallet txs; agent keys only with explicit user approval and a funded throwaway EVM key (
RH_EVM_PRIVATE_KEY). - Confirm name/symbol/value/chain with the user before broadcast.
- Ensure chain id 4663 before send.
- Value must cover
launchFee+ gas; do not underpay. - Never claim the token is Solana
$CLAWDor 1:1 backed. - Never use Solana keypairs for this path.
- Do not use RH bonding factory (
0x3f60…) for Pons launches. - If
launchEnabledis false orgetLaunchConfig(0).enabledis false — stop and report.
Env
| Variable | Role |
|---|---|
RH_RPC_URL |
Preferred RH JSON-RPC |
RH_EVM_PRIVATE_KEY |
Optional agent custody hex key (never commit) |
PONS_LAUNCH_FACTORY |
Override factory (else registry default) |
VITE_PONS_LAUNCH_FACTORY |
Browser mirror of factory override |
Install this skill elsewhere
cp -R .agents/skills/pons-launch \
~/.agents/skills/pons-launch
# Portable repo mirror
cp -R .agents/skills/pons-launch \
skills/pons-launch
Portable duplicate: skills/pons-launch/SKILL.md in this repository.
Related
- Bonding-curve RH launches: skill
rh-bonded-launch - Cheshire Pons page:
client/src/pages/PonsPage.tsx - Server routes:
server/routes/pons.ts - Firecrawl monitor only:
server/lib/pons-firecrawl.ts(not required for launch)