npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@frontierfun/sdk

v1.1.0

Published

TypeScript SDK for the Frontier launchpad — deploy, trade, graduate, stake, and manage coins programmatically (bonding curve → Uniswap V4 pool)

Readme

@frontierfun/sdk

TypeScript SDK for Frontier, the coin launchpad on Robinhood Chain. Launch coins, trade them on their bonding curve and on their Uniswap V4 pool, graduate them, provide liquidity, stake, manage fee recipients, referrals, highlights and vesting, from a bot, a backend or an AI agent framework.

A coin on Frontier either sells along a bonding curve and graduates into its own locked Uniswap V4 pool, or launches instantly straight into that pool. Both modes register the pool in the deploy transaction; isLPd says where the coin trades. Creators pick the pool fee model, optional hook extensions and a staking vault at creation; the rules are frozen afterwards and the liquidity is locked forever.

This package is the single source of truth: the MCP server and the OpenClaw plugin are thin adapters over its ToolExecutor (84 tools: 50 read, 30 write, 4 config; see the API reference).

Requirements

  • Node.js >= 24
  • ethers v6 (dependency), zod v4 (dependency)

Installation

npm install @frontierfun/sdk
# or
pnpm add @frontierfun/sdk

Quick Start

import { LaunchpadAgent } from '@frontierfun/sdk'

// Robinhood Chain (4663) by default; every contract, the API and the explorer come from the chain registry
const agent = new LaunchpadAgent({
  rpcUrl: 'https://rpc.mainnet.chain.robinhood.com',
  privateKey: process.env.FRONTIER_PRIVATE_KEY, // omit for read-only mode
})

// Or from the live registry (GET /chains + the extension catalog); static fallback with a warning
const testnet = await LaunchpadAgent.fromRegistry({ chainId: 421614, privateKey: process.env.FRONTIER_PRIVATE_KEY })

const state = await agent.getTokenState('0xCOIN...')
console.log(state.launchMode, state.isLPd, state.poolId)

if (!state.isLPd) {
  const quote = await agent.quoteBuy('0xCOIN...', '0.5') // fee-inclusive: pass what you will send
  const result = await agent.buy('0xCOIN...', '0.5') // path 'bonding-curve' (auto-routes to the pool once isLPd)
} else {
  const quote = await agent.quotePoolSwap({ tokenAddr: '0xCOIN...', side: 'buy', amountIn: '0.5' })
  const result = await agent.poolSwap({ tokenAddr: '0xCOIN...', side: 'buy', amountIn: '0.5', slippageBps: 100 })
}

Amounts in the agent API are decimal strings ('0.5' = 0.5 ETH, '1000' = 1000 coins) and results are bigint wei unless documented otherwise. The pool module (pool.ts) works in wei; the agent converts.

Configuration

ConnectionConfig

new LaunchpadAgent(conn: ConnectionConfig, config?: DeepPartial<FullConfig>). Everything except rpcUrl has a default derived from the chain registry (chains.ts, a verified snapshot of the Frontier API's GET /chains). resolveConnectionConfig(conn) is exported and pure: it applies the defaults, checksums every address and throws on an unknown chain, a malformed URL or a bad address (a typo must fail loudly rather than fall back to production addresses on the wrong network).

| Field | Default | Notes | MCP server env | |-------|---------|-------|----------------| | rpcUrl | required | http(s):// or ws(s)://. Strategies and live event feeds need wss:// | FRONTIER_RPC_URL | | privateKey | none | Omit for read-only mode (every write throws) | FRONTIER_PRIVATE_KEY / FRONTIER_KEYSTORE_PASSWORD | | chainId | 4663 | 4663 Robinhood Chain, 421614 Arbitrum Sepolia | FRONTIER_CHAIN_ID | | chain | registry entry for chainId | A full FrontierChainConfig (from fetchChainRegistry) to use instead of the static one; set by fromRegistry | | | factoryAddress | chain contracts.tokenFactory | BCTokenFactory | FRONTIER_FACTORY_ADDRESS | | bondingCurveAddress | contracts.bondingCurve | | FRONTIER_BONDING_CURVE_ADDRESS | | curveMathAddress | contracts.curveMath | | FRONTIER_CURVE_MATH_ADDRESS | | highlightsManagerAddress | contracts.highlightsManager | | FRONTIER_HIGHLIGHTS_ADDRESS | | referralManagerAddress | contracts.referralManager | | FRONTIER_REFERRAL_ADDRESS | | vestingAddress | contracts.tokenVesting | | FRONTIER_VESTING_ADDRESS | | hookAddress | contracts.univ4.hook | FactoryHook, the coin's Uniswap V4 hook | FRONTIER_HOOK_ADDRESS | | liquidityManagerAddress | contracts.liquidityManager | Optional; on-chain pool key lookup (getPoolKeyOnChain) | FRONTIER_LIQUIDITY_MANAGER_ADDRESS | | poolManagerAddress | contracts.univ4.poolManager | | FRONTIER_POOL_MANAGER_ADDRESS | | universalRouterAddress | contracts.univ4.universalRouter | | FRONTIER_UNIVERSAL_ROUTER_ADDRESS | | quoterAddress | contracts.univ4.quoter | V4Quoter | FRONTIER_QUOTER_ADDRESS | | positionManagerAddress | contracts.univ4.positionManager | | FRONTIER_POSITION_MANAGER_ADDRESS | | stateViewAddress | contracts.univ4.stateView | Optional lens; PoolManager.extsload is the primary read path | FRONTIER_STATE_VIEW_ADDRESS | | permit2Address | contracts.permit2 | Same address on every chain | FRONTIER_PERMIT2_ADDRESS | | stakingVaultFactoryAddress | contracts.stakingVaultFactory | | FRONTIER_STAKING_VAULT_FACTORY_ADDRESS | | harvesterAddress | contracts.harvester | | FRONTIER_HARVESTER_ADDRESS | | polDistributorAddress | contracts.polDistributor | | FRONTIER_POL_DISTRIBUTOR_ADDRESS | | cloneFactoryAddress | contracts.cloneFactory | | FRONTIER_CLONE_FACTORY_ADDRESS | | bcTokenDeployerAddress | contracts.bcTokenDeployer | Optional; predicts a coin's address before the deploy | | | wethAddress | native.wrapped | Reward asset of vaults and referrals (never a pool currency) | FRONTIER_WETH_ADDRESS | | poolFee | DYNAMIC_FEE_FLAG (0x800000) | Fee field of every Frontier pool key | | | poolTickSpacing | 60 | | | | extensionAddresses | chain extensions | { dynamicFee, sniperTax, maturityCurve } overrides | | | routerVariant | detected | 'legacy' or 'minHopPrice'; config override > chain default > bytecode detection | | | apiBaseUrl | chain apiBaseUrl | https://api.frontier.fun / https://dev-sepolia.api.frontier.fun | FRONTIER_API_BASE_URL | | rpcTimeoutMs | 15000 | HTTP only | | | orderSplitter | off | Opt-in liquidity-aware chunking of large curve buys | | | stateBackend / statePersistDir | none | State persistence (see Advanced) | | | explorerBaseUrl | chain explorerUrls[0] | Origin; a bare host is assumed https | FRONTIER_EXPLORER_URL | | referralBaseUrl | chain appBaseUrl | Referral links are ${referralBaseUrl}?ref=${address} | FRONTIER_REFERRAL_BASE_URL | | referrerAddress | agent address | | FRONTIER_REFERRER_ADDRESS | | expectedChainId | chainId | Verified against provider.getNetwork() before the first write; null disables (local forks only) | |

The SDK itself reads only the wallet variables (through WalletManager); the FRONTIER_* column is what the MCP server maps onto these fields. connectionConfigForChain(chainId) (from ./constants) returns the same defaults as a plain object you can spread into a ConnectionConfig.

import { LaunchpadAgent, connectionConfigForChain, resolveConnectionConfig } from '@frontierfun/sdk'

const testnetAgent = new LaunchpadAgent({ ...connectionConfigForChain(421614), privateKey })
const resolved = resolveConnectionConfig({ rpcUrl: 'https://rpc.mainnet.chain.robinhood.com' })
resolved.chain.contracts.univ4.hook // '0xb31780AAd49D3Cc7Dd6E03E9e462606F0A5A30Cc'

Providers are created with { staticNetwork: true, cacheTimeout: -1 }: ethers otherwise dedupes identical reads for 250 ms and returns a stale balance right after a transaction on a ~250 ms-block chain. Do the same for any provider you build yourself.

LaunchpadAgent.fromRegistry(opts, config?)

Static async factory: fetches GET /chains (and the extension catalog for the chain) from opts.apiBaseUrl (default: the chain's) and builds the agent on the live registry entry. rpcUrl defaults to the chain's first HTTP RPC. When the API is unreachable it logs a warning and falls back to the static registry, so a bot still starts.

FullConfig (behaviour)

The second constructor argument, also settable at runtime with updateConfig(partial) / setTrading / setRisk / setCopy / setStrategies / setOps; getConfig() returns the effective config. createDefaultConfig(), mergeConfig(), validateConfigBounds() and ABSOLUTE_MINIMUM_FLOORS are exported (@frontierfun/sdk/config).

| Section | Fields (defaults) | |---------|-------------------| | trading | defaultSlippageBps (200), defaultAffiliate (zero address), maxTradeETH ('1.0'), gasReserve ('0.05'), gasPriceMultiplier (1.0), gasLimitOverride (0 = estimate), autoApprove (true), maxApprovalAmount | | risk | maxTotalExposure, stopLossPct, takeProfitPct, tradeCooldownMs, maxOpenPositions, minHoldTimeMs | | copy | targets, scaleFactor, delayMs, maxCopyETH, minCopyETH, copyBuys, copySells, slippageBpsOverride, selfAffiliate, tokenWhitelist, tokenBlacklist, copyPoolSwaps (false) | | strategies | sniper (ethPerSnipe, maxReserveToBuy, delayAfterDeployMs, slippageBps?, includeInstant false), momentum, graduation, exitMonitor | | ops | logging, logLevel, dryRun (false), webhookUrl |

maxTradeETH caps every buy, triggerGraduation and poolSwap (which throws rather than silently capping); gasReserve is kept back on every write; dryRun returns a typed DryRunResult (isDryRunResult()) instead of sending.

Launching a coin

uploadImage(pathOrDataUrl, name?)

Uploads a PNG/JPEG/GIF/WebP (2 MiB decoded max) to the Frontier API (POST /upload) and returns ipfs://<cid>. File paths must sit under ./uploads (agent.uploadBaseDir, relative to the working directory); data URLs are accepted.

deploy(cfg: DeployConfig) and buildDeploy(cfg)

buildDeploy builds and validates everything the deploy sends without sending it (BuildDeployResult { args, valueWei, creationFee, devBuyWei, launchMode, curve?, seedTick?, predictedTokenAddress?, predictedPoolId? }); deploy sends it (gas limit = estimate × 1.25) and returns DeployResult { txHash, tokenAddress, poolId, launchMode, hookConfig, launchConfig, valueWei, devBuy?: { ethAmount, tokenAmount }, stakingVault?, predictedTokenAddress? }. msg.value = creationFee + devBuyWei.

| DeployConfig field | Meaning | |----------------------|---------| | name, symbol, description | Metadata; the factory limits are 32 / 16 / 256 characters (METADATA_LIMITS) | | image | ipfs://<cid>, a bare CID or an https URL (required; use uploadImage) | | twitter?, telegram?, website? | Packed into the on-chain description like the app (packDescription) | | communityFeeRatio? | 0 without a vault, 1..100 with one (default 50 when stakingConfig.deployStaking) | | salt? | bytes32 hex or a memorable string (keccak'd); random when omitted | | stakingConfig? | { deployStaking, alternativeFeeRecipient? } | | launch? | { mode: 'curve', virtualReserves?, initialSupply?, startingFdvEth?, raiseTargetEth? } or { mode: 'instant', seedTick?, startingFdvEth? }. Default: bonding curve with the factory's initialParams() | | devBuyEth? / devBuyPercent? | Creator buy sent on top of the creation fee (a curve buy or a pool swap); percent is capped by maxCreatorBuyBps | | hookConfig? | 'default' (the app default: 1% base fee, 70% LP share, dynamic-fee extension, no sniper window), 'none' (0x: flat 0.30%, no extensions), raw hex, or a HookConfigV2Input. Default 'default' | | fixedFee? | Base fee in pips (default 10000 = 1%; ceiling MAX_HOOK_FEE = 10%) | | lpShareBps? | LP share of the swap fee (default 7000) | | feeMode? | 'dynamic' binds the dynamic-fee calculator (default), 'fixed' uses the base fee alone | | dynamicFee? | { floorFee, midFee, capFee } pips, strictly ascending | | extensions? | [{ id \| address, params?, calls?, kind? }], resolved through the extension catalog |

Extension ids (from GET /extensions, staticCatalog(chain) as fallback): dynamic-fee (floorFee, midFee, capFee), maturity-curve (ages, fees or steps), sniper-tax (peakFee, window). dynamic-fee and maturity-curve are mutually exclusive base-fee providers; a sniper tax whose peak exceeds MAX_HOOK_FEE needs a declared sniper window (derived automatically).

const image = await agent.uploadImage('./coin.png')

// Bonding curve with the creator knobs
const curveCoin = await agent.deploy({
  name: 'Mittens', symbol: 'MITTENS', description: 'Cat coin', image,
  launch: { mode: 'curve', startingFdvEth: '2', raiseTargetEth: '5' },
  devBuyEth: '0.1',
  stakingConfig: { deployStaking: true }, communityFeeRatio: 50,
})

// Instant launch straight into the pool, fixed fee, sniper tax for the first 10 minutes
const instantCoin = await agent.deploy({
  name: 'Rocket', symbol: 'RKT', description: 'Instant', image,
  launch: { mode: 'instant', startingFdvEth: '10' },
  fixedFee: 5000, feeMode: 'fixed',
  extensions: [{ id: 'sniper-tax', params: { peakFee: 200000, window: 600 } }],
})

Factory reads

  • getFactoryParams(): FactoryParams { initialParams: CurveParams, curveBounds: CurveBounds, seedFdvBounds: { minFdv, maxFdv }, feeConfig: { protocolFee, creatorFee, refundFee, creationFee }, maxCreatorBuyBps, creatorShareBps, txFee, treasury? } (30 s cache). Governance values: read them, never hardcode them.
  • getCreationFee(): bigint, isProtocolPaused(): boolean.
  • getExtensionCatalog(): ExtensionCatalogEntry[] (GET /extensions?chainId, 5 min cache, static fallback), primeExtensionCatalog(catalog).

Bonding curve trading

Curve quotes are fee-inclusive: pass the full ETH you will send to quoteBuy and it returns the coins delivered; quoteSell returns the net ETH you receive. The curve fee is txFee bps of the ETH side (fee-on-total); CurveFeeDistributed splits it referral / creator (creatorShareBps of the rest) / protocol.

  • buy(tokenAddr, ethAmount, overrides?), sell(tokenAddr, tokenAmount, overrides?) -> TradeResult { txHash, amount, spent, priceAfter, graduated, grossAmount, curveFee, protocolFee (alias of curveFee), netAmount, feeBps, path: 'bonding-curve' | 'v4-pool', feeSplit?, poolId?, feePips? }. Both auto-route to the Uniswap V4 pool once the coin is isLPd (the affiliate is dropped with an audit warning; on the pool path curveFee is the hook fee and feeBps = feePips / 100).
  • TradeOverrides { slippageBps?, affiliate?, gasLimit?, gasPriceMultiplier? }.
  • quoteBuy(tokenAddr, ethAmount) -> QuoteBuyResult { tokensOut, costAfterFee, grossAmount, curveFee, netAmount, feeBps, wouldGraduate, supplyRemaining, ethToGraduation, feeSplit?, graduationFees? } (throws once the coin is isLPd: use quotePoolSwap).
  • quoteSell(tokenAddr, tokenAmount) -> QuoteSellResult { ethOut, fee, grossAmount, curveFee, netAmount, feeBps, feeSplit? }.
  • calculateCost(tokenAddr, tokenAmount) -> CalculateCostResult { grossCost, curveFee, netCostWithFee, feeBps, tokenAmount } (ETH to buy exactly that many coins, fee included).
  • estimateGraduation(tokenAddr) -> GraduationEstimate { supplyRemaining, ethNeeded, callerFeeETH, creatorFeeETH, protocolFeeETH }: graduation pays REFUND_FEE bps of the raise to whoever fills the curve, CREATOR_FEE bps to the fee recipient and PROTOCOL_FEE bps to the treasury.
  • triggerGraduation(tokenAddr, overrides?): a buy of ethNeeded × 1.01 (default slippage 500 bps), capped by maxTradeETH.
  • canOTCTransfer(tokenAddr, from, to) -> { allowed, reason } (isLPd, or from/to is the factory or the vesting contract) and otcTransfer(tokenAddr, to, amount): curve coins are locked until graduation.

Uniswap V4 pool

Every coin has one hooked pool: PoolKey { currency0: native ETH (address(0)), currency1: coin, fee: DYNAMIC_FEE_FLAG, tickSpacing: 60, hooks: FactoryHook }, poolId = keccak256(abi.encode(poolKey)). Buys are zeroForOne. There is no WETH on the pool path; sells need Permit2 approvals, set up on demand.

  • getPoolKey(tokenAddr): PoolKey, getPoolId(tokenAddr): string (local, no RPC), getPoolKeyOnChain(tokenAddr) (LiquidityManager, when configured).
  • getPoolState(tokenAddr): PoolState { poolId, poolKey, initialized, tradable (= isLPd), launchMode, sqrtPriceX96, tick, liquidity, priceEthPerCoin, coinPerEth, fdvEth, hook: HookPoolState { registered, coin, communityFeeRatio, stakingVault, fixedFee, lastAppliedFee, lpShareBps, sniperWindow, feeCalculators, observers, referenceTick, lastSwapTimestamp, volatilityAccumulator, ... }, fee: PoolFeePreview & { source, previewAmountWei }, protocolFeeRatio, lpdAt }. Slot0/liquidity are read through PoolManager.extsload (StateView is the fallback); a curve coin's pool is initialized but dormant until graduation, so tradable is the truth, never initialized.
  • previewPoolFee(tokenAddr, { side, amountIn? | amountOut? }) -> PoolFeePreview { totalFeePips, lpFeePips, nonLpFeePips, totalFeeBps } (FactoryHook.previewFee). Pool fees are dynamic: quote them for the actual size, never assume them.
  • quotePoolSwap({ tokenAddr, side, amountIn? | amountOut?, hookData? }) -> PoolQuote { poolId, side, mode: 'exact-in' | 'exact-out', amountIn, amountOut, gasEstimate, feePips, fee, priceEthPerCoin, executionPriceEthPerCoin } (V4Quoter, runs the hook).
  • poolSwap({ tokenAddr, side, amountIn? | amountOut?, slippageBps?, deadlineSeconds? (1200), recipient?, hookData? }) -> PoolSwapResult { txHash, side, mode, amountIn, amountOut, poolId, path: 'v4-pool', recipient, feePips, gasUsed, amountsFromLog, sqrtPriceX96After?, swapLog? } through the Universal Router (V4_SWAP + SETTLE_ALL / TAKE_ALL; exact-out buys add a SWEEP that refunds the unused ETH). amountIn is what the swapper paid, hook fee included; swapLog carries the raw PoolManager Swap deltas (swapper-side sign convention: negative = paid in, positive = received; the log's input leg is net of the hook's non-LP fee). Gated by maxTradeETH, the ETH balance and the coin balance.
  • ensurePoolSellApproval(tokenAddr, amount?) -> { txHashes }: ERC-20 approve to Permit2 (max) and Permit2.approve(coin, universalRouter, MAX_UINT160, MAX_UINT48), skipped when already sufficient. poolSwap calls it before a sell.
  • hookData: optional bytes (<= 256) forwarded to the hook; encodeBeneficiaryHookData(address) is the observer-attribution convention (abi.encode(address)).

The Universal Router on Robinhood Chain decodes the newer 6-field exact-input params (minHopPriceX36), Arbitrum Sepolia the legacy 5-field ones; the agent resolves routerVariant once (config, then chain default, then bytecode detection). With empty hookData both encodings work by accident; with non-empty hookData only the right one does.

Liquidity (Uniswap V4 PositionManager)

Positions target the coin's one hooked pool (there is no "default pool"). Native ETH is sent as msg.value (the surplus is swept back), the coin is pulled through Permit2 (ensurePoolLpApproval), everything goes through PositionManager.modifyLiquidities.

  • addLiquidity({ tokenAddr, amountEthMax, amountTokenMax, positionTokenId?, tickLower?, tickUpper?, tickPreset? ('full' | 'narrow' ~±5% | 'wide' ~±25%), slippageBps?, deadlineSeconds?, recipient? }) -> AddLiquidityResult { txHash, positionTokenId, liquidity, positionLiquidity, amount0, amount1, tickLower, tickUpper, poolId, valueWei, minted }. Mints, or increases when positionTokenId is given.
  • removeLiquidity({ positionTokenId, liquidityPct? (100) | liquidity?, amount0Min?, amount1Min?, slippageBps?, deadlineSeconds?, recipient?, burn? }) -> RemoveLiquidityResult { txHash, positionTokenId, amount0, amount1, liquidityRemoved, remainingLiquidity, burned, poolId }.
  • collectFees({ positionTokenId, recipient? }) -> CollectFeesResult { txHash, positionTokenId, amount0, amount1, poolId }.
  • getLpPositions(owner?, tokenAddr?, opts?: PositionScanOptions { fromBlock?, toBlock?, chunkSize? }) -> LpPosition[] { tokenId, poolId, poolKey, tokenAddress, tickLower, tickUpper, liquidity, amount0, amount1, inRange }. The PositionManager has no enumerable extension: positions are found from its Transfer logs (from KNOWN_POSITION_MANAGER_START_BLOCK for the chain), which needs an RPC that serves eth_getLogs.

Protocol-owned liquidity, fee recipients, extensions

  • collectPoolFees(tokenAddr, opts?: { tokenIds? } & PositionScanOptions) -> CollectPoolFeesResult { txHash, token, tokenIds, collected[], distributed[], deferred[], totalWeth, totalCoin }: permissionless PolDistributor.multiCollect over the coin's locked positions (found from the LiquidityManager mints when tokenIds is not given). The creator's share of the pool fees reaches the fee recipient (and the vault) this way.
  • getFeeRecipient(tokenAddr) (BCToken.getFeeRecipient(): the alternative recipient or the creator), setFeeRecipient(tokenAddr, recipient) -> { txHash, recipient } (only the current recipient may change it, per the contract).
  • createFeeRecipient(params) -> CreateFeeRecipientResult { txHash, clone, kind, implId, initData, deterministic } with params one of { kind: 'lottery', coin?, salt?, implId? }, { kind: 'buyback-burn', coin, shareRecipient?, shareBps? (<= 5000), salt?, implId? }, { kind: 'fee-splitter', coin, payees, shares (bps summing to 10000), salt?, implId? }. Clones come from the chain's CloneFactory (RECIPIENT_IMPL_IDS); the SDK checks implementationOf(implId) first and throws "No '' fee-recipient implementation is registered on this chain" when the chain has none (production today; Arbitrum Sepolia has the lottery implementation). predictFeeRecipientAddress and encodeFeeRecipientInitData are exported for salted deployments.
  • getCoinExtensions(tokenAddr) -> CoinExtensionsState & { source: 'api' | 'chain' }: the API's /coins/{addr}/extensions payload (pool id, hook config, bound fee calculators and observers with catalog joins), rebuilt from getPoolState when the API is unavailable.

Staking (vaults with a WETH stream)

Vault shares have 21 decimals on Frontier vaults: the SDK reads vault.decimals() (getVaultDecimals, cached) and every share amount goes through it. requestUnstake burns shares immediately, escrows the assets in the cooldown holder and restarts the cooldown for the whole escrow.

  • getStakingVaultAddress(tokenAddr): string | null (FactoryHook.getPoolState(poolId).stakingVault, then the API's /vaultByToken).
  • previewStake(vault, amount): bigint (shares), previewUnstake(vault, amount): bigint (assets), formatShares(vault, shares): string.
  • stake(vault, tokenAddr, amount) -> { txHash, shares }, requestUnstake(vault, amount), requestUnstakeByShares(vault, shares) (decimal, vault decimals), completeUnstake(vault, receiver?), getCooldownStatus(vault) -> { amount, cooldownEnd, isReady }.
  • getStakingPosition(vault) -> StakingPosition { shares, sharesDecimals, sharesFormatted, assets, maxWithdraw, maxRedeem, cooldownDuration, earnedWeth, pendingWeth }.
  • getStakingRewards(vault) -> { earnedWeth, pendingWeth }, claimStakingRewards(vault) -> { txHash, wethAmount } (StakingVault.claimWeth()).

Referral, highlights, vesting

  • Referral: referralLink(referrer?) -> ${referralBaseUrl}?ref=${address} (https://frontier.fun?ref=0x...), getReferralFees() -> { directBps, indirectBps, totalBps }, getReferralBpsFor(user), getReferralChain(user) -> { direct, indirect }, getPendingReferralReward(rewardToken = WETH), claimReferralReward(rewardToken = WETH).
  • Highlights: getHighlightConfig() -> { minDuration, hardCap, baseFeePerSecond, cooldownPeriod, expThreshold }, getHighlightStatus(tokenAddr, duration) -> { bookedUntil, tokenCooldownUntil, isSlotFree, isTokenEligible, quotedFee }, highlightToken(tokenAddr, durationSeconds) -> { txHash, fee } (MAX_HIGHLIGHT_DURATION_SECONDS bounds it).
  • Vesting: createVestingSchedule({ token, beneficiaries, amounts, startTimestamp, cliffDurationSeconds, totalDurationSeconds, slicePeriodSeconds }) -> { txHash, scheduleIds }, releaseVestedTokens(token, scheduleId) -> { txHash, amount }, getVestingInfo(token, index?), getAllVestingSchedules(token).

Reads

  • getTokenState(tokenAddr): TokenState { address, name, symbol, description, imageURI, totalSupply, maxSupply, initialSupply, virtualBalance, targetETH, reserveBalance, tvl, isLPd, lpAddress, creator, pricePerToken, protocolFee, creatorFee, refundFee, communityFeeRatio, launchMode, directSeed, lpdAt, poolId, feeRecipient, alternativeFeeRecipient, curve: { virtualReserves, initialSupply, maxSupply, targetETH }, graduationFees: { toCreator, toProtocol, toGraduationCaller }, pool?: PoolState }. lpAddress is BCToken.lp(), which on Frontier is the hook address, not a pool: use poolId. pool is filled when isLPd (best effort).
  • hasGraduated(tokenAddr), getTokenBalance(tokenAddr), getETHBalance(), getWETHBalance(), isRPCHealthy(timeoutMs?).
  • Getters: address, chainId, chain (FrontierChainConfig), connection (ResolvedConnectionConfig), apiBaseUrl, explorerBaseUrl, referralBaseUrl, referrerAddress, wethAddress, uploadBaseDir, readOnly, hasWebSocket, txQueue; contract accessors factory, bondingCurve, curveMath, highlights, referral, vesting, hook, poolManager, universalRouter, quoter, positionManager.

Events

Typed subscriptions through the EventBus (fields read by name, never positionally); each returns an unsubscribe function. A wss:// RPC gives push delivery; HTTP falls back to ethers polling, which needs an RPC that serves eth_getLogs.

  • onNewCoin(cb: NewCoinEvent { creator, token, name, symbol, description, image, lp (hook), initialSupply, maxSupply, initialETHReserves, initialPrice, initialMarketCap, targetETH, directSeed, launchMode })
  • onBuy(cb: BuyEvent), onSell(cb: SellEvent) (amount, amountOut, totalSupply, price, marketCap, reserveBalance), onGraduation(cb: GraduationEvent { token, pool (hook) })
  • onCurveFeeDistributed(cb, tokenAddr?) (totalFee, referralAmount, creatorAmount, protocolAmount, feeRecipient)
  • onPoolSwap(tokenAddr | undefined, cb: PoolSwapEvent { poolId, sender (the router, not the trader), amount0, amount1, sqrtPriceX96, liquidity, tick, fee (LP fee pips), ethAmount, coinAmount, side }): PoolManager Swap filtered by pool id (all Frontier pools when tokenAddr is undefined)
  • onSwapFeeDistributed(tokenAddr | undefined, cb) (the hook's non-LP fee leg: protocolAmount, vaultAmount, recipientAmount, ...)
  • removeAllListeners(), waitForEventSubscriptions(), destroy()

Strategies and copy trading

StrategyRunner runs the built-in strategies (STRATEGY_CLASSES: LaunchSniper, Momentum, GraduationSniper, ExitMonitor, CopyTrader); they need a wss:// RPC. CopyTrader / CopyTradeMonitor mirror curve trades of target wallets (pool swaps opt-in with copy.copyPoolSwaps, keyed on coins the target already traded on the curve because the PoolManager Swap event names the router, not the trader). See the skill's strategy notes.

import { StrategyRunner } from '@frontierfun/sdk/strategies'
const runner = new StrategyRunner(agent, gate) // TxGate optional
agent.setStrategyRunner(runner)
await runner.startByName('GraduationSniper')

AI agent integration

ToolExecutor and TOOL_DEFINITIONS

TOOL_DEFINITIONS (84 tools) is the framework-agnostic catalog: name, category (read | write | config), description, JSON-schema parameters, return description. ToolExecutor validates the parameters (TOOL_SCHEMAS, zod), scans freeform fields for prompt injection, resolves token_identifier (address, symbol or name through the TokenRegistry), applies the TxGate to writes, runs pre-flight checks and executes against the agent.

import { LaunchpadAgent, ToolExecutor, TxGate, TokenRegistry, TxHistory, TOOL_DEFINITIONS } from '@frontierfun/sdk'

const gate = new TxGate({ maxPerTxETH: '0.5', dailyBudgetETH: '3', cooldownSeconds: 2 })
const registry = new TokenRegistry(agent.apiBaseUrl, agent, { pollIntervalMs: 60_000 })
registry.start()
const tools = new ToolExecutor(agent, { gate, tokenRegistry: registry, txHistory: new TxHistory(100), toolPrefix: 'frontier_' })

const state = await tools.execute('get_token_state', { token_identifier: 'MITTENS' })
const swap = await tools.execute('pool_swap', { token_identifier: 'MITTENS', side: 'buy', amount_in: '0.1' })

ToolExecutorOptions { gate?, toolPrefix?, signal?, strategyRunner?, tokenRegistry?, txHistory? }. Results are ToolResult { success, data?, error?, simulatedRevert?, revertReason?, revertCode? } with amounts formatted as decimal strings. buildDeployConfig maps deploy_token parameters onto a DeployConfig.

TxGate

new TxGate({ maxPerTxETH, dailyBudgetETH, cooldownSeconds, tokenBlacklist, confirmAboveETH, enabled }, ceilings?): per-transaction limit, rolling 24h budget, cooldown, blacklist, optional confirmation callback (ConfirmFn); reservations are rolled back on failure; getStats() feeds spending_stats. Operator ceilings bound what update_tx_gate may set; ABSOLUTE_MINIMUM_FLOORS cannot be crossed.

TokenRegistry

new TokenRegistry(apiBaseUrl, agent /* onNewCoin source */, options?, signal?, log?): coin cache bootstrapped from /coinsPage (newest first, up to 20 pages of 100), refreshed every 60 s and on onNewCoin; resolve(query) (async; server-side search fallback), resolveLocal(query), getTokenList(options), getRecentDeployments(limit), getInitStatus(). Ambiguous symbols resolve to the highest market cap by default and report the alternatives.

Wallet management

import { WalletManager } from '@frontierfun/sdk/wallet'
const { wallet, source } = await new WalletManager().resolve({ keystorePassword: process.env.FRONTIER_KEYSTORE_PASSWORD })

Priority: FRONTIER_PRIVATE_KEY > explicit config > unprefixed PRIVATE_KEY; with a password and no keystore the key is migrated into the encrypted keystore ~/.frontier/agent-wallet.enc (AES-256-GCM, scrypt) and the env var must then be removed; with neither, a wallet is generated. The audit log lives in ~/.frontier/audit (DEFAULT_AUDIT_DIR, FRONTIER_AUDIT_DIR in the adapters), agent state in ~/.frontier/state.

Frontier data API client

Every function takes (baseUrl, ..., signal?) and validates the response with zod; amounts arrive as decimal ETH strings, addresses checksummed. Base URLs: https://api.frontier.fun (4663), https://dev-sepolia.api.frontier.fun (421614); the API is server-side only (no CORS for third parties) and has no third-party WebSocket feed.

| Function | Endpoint | |----------|----------| | fetchChainRegistry | GET /chains (ServedChain[], mergeRegistryChain merges into the static entry) | | fetchExtensionCatalog | GET /extensions?chainId (extensionAddressesFromCatalog) | | fetchCoinsPage, fetchAllCoins, fetchTokenList | GET /coinsPage (sort: activity, newest, oldest, marketcap, volume24h, trending; filter: all, new, bonded, trending, instant; search; max 100 per page, offset <= 10000) | | fetchCoinsHydrate | GET /coinsHydrate?addresses&full=1 (<= 100 addresses) | | fetchCoinsStats, fetchCoinsTop | GET /coinsStats, GET /coinsTop (kind: 'closest' = nearest graduation) | | fetchCoinEvents, fetchCoinCandles | GET /coinEvents?tokenAddress&limit, GET /coinCandles (COIN_CANDLE_INTERVALS) | | fetchCoinExtensions | GET /coins/{addr}/extensions?chainId | | fetchRecentActivity, fetchBondedActivity | GET /recentActivity, GET /bondedActivity | | fetchPoolTxHistory | GET /pools-v4/{poolId}/history/txs | | fetchTokensVolume | GET /tokens | | fetchVaults, fetchVaultByToken, fetchVaultEventsByToken, fetchVaultPosition | GET /vaults, /vaultByToken, /vaultEventsByToken, /vaultPositionByUserVault | | fetchReferralRewards, fetchReferralPayouts | GET /referralRewards, /referralPayouts | | fetchHighlights, fetchCurrentHighlight | GET /highlights, /currentHighlight | | fetchDistribution, fetchHoldings, fetchCreations | app surfaces (/distribution, /holdings, /creations; unstable) | | uploadImage, validateUploadPath, ipfsGatewayUrl | POST /upload (MAX_UPLOAD_FILE_SIZE = 2 MiB), GET /ipfs/{cid} |

Errors are classified by classifyAPIError (APIError, APIErrorCategory; RATE_LIMITED carries retryAfterSeconds, no automatic retry).

Chain registry, Uniswap V4 and launch helpers

  • @frontierfun/sdk/chains: CHAINS, chainConfig(chainId), isSupportedChain, explorerBaseUrl, ROBINHOOD_CHAIN (4663), ARBITRUM_SEPOLIA_CHAIN (421614), DEFAULT_CHAIN_ID, SUPPORTED_CHAIN_IDS, DYNAMIC_FEE_FLAG, FRONTIER_POOL_TICK_SPACING, NATIVE_CURRENCY, PERMIT2_ADDRESS, MULTICALL3_ADDRESS, types FrontierChainConfig, FrontierContractAddresses, FrontierUniV4Addresses, FrontierExtensionAddresses, FrontierCurveConfig.
  • @frontierfun/sdk/v4: buildPoolKey, computePoolId, normalizePoolId, isFrontierPoolKey, TickMath (getSqrtPriceAtTick, getTickAtSqrtPrice, alignTickDown/Up, fullRangeTicks, MIN_TICK/MAX_TICK, MODULATED_MIN_TICK), price helpers (ethPerCoinFromSqrtPrice, fdvWeiAtSqrtPrice, fdvWeiAtTick, sqrtPriceFromEthPerCoinWad), StateLibrary slot reads (poolStateSlot, decodeSlot0, readPoolSlotState, readPoolSlotStateViaStateView), Universal Router encoders (encodeV4SwapExactInSingle, encodeV4SwapExactOutSingle, encodeUniversalRouterCommands, encodeUniversalRouterSweep, Actions, UR_COMMAND_V4_SWAP), PositionManager encoders (encodeMintPosition, encodeIncreaseLiquidity, encodeDecreaseLiquidity, encodeBurnPosition, encodeSettlePair, encodeTakePair, encodeCloseCurrency, encodeSweep, encodeModifyLiquidities, decodePositionInfo, positionMatchesPool), LiquidityAmounts (getLiquidityForAmounts, getAmountsForLiquidity, getAmount0Delta, getAmount1Delta), detectUniversalRouterVariant, instantDevBuyValueWei.
  • @frontierfun/sdk/launch: encodeHookConfig / decodeHookConfig, defaultHookConfigInput, buildHookConfigInput, encodeDynamicFeeConfig, encodeSniperTaxConfig, encodeMaturityCurveConfig, encodeExtensionConfig, staticCatalog, curveParamsFromKnobs, describeCurve, validateCurveParams, curveBoundsFromConfig, curveDefaultsFromConfig, seedTickFromFdv, validateSeedTick, devBuyTokens, instantDevBuyQuote, packDescription / unpackDescription, validateMetadata, toIpfsUri, normalizeSalt, deployArgsToArray, curveLaunchConfig, instantLaunchConfig, constants (METADATA_LIMITS, MAX_HOOK_FEE, SNIPER_MAX_FEE, DEFAULT_LP_SHARE_BPS, PROTOCOL_FLOOR_PIPS, DYNAMIC_FEE_DEFAULTS, APP_DEFAULT_FIXED_FEE, ...).
  • pool.ts (root export): the PoolContext-based operations the agent delegates to (getPoolState, previewPoolFee, quotePoolSwap, poolSwap, ensurePermit2Approval, addLiquidity, removeLiquidity, collectFees, getLpPositions, findLockedPositionIds, collectPoolFees, createFeeRecipient, ...) plus their types and constants (DEFAULT_POOL_SLIPPAGE_BPS, KNOWN_POSITION_MANAGER_START_BLOCK, RECIPIENT_IMPL_IDS).

Error decoding

decodeRevertData(data), extractRevertData(error), innermostRevert, humanizeRevert(decoded) and KNOWN_REVERT_NAMES cover the Frontier contracts' custom errors and the Uniswap V4 ones (getRevertDecoderInterface()); the agent applies them to every failed write, and runPreflightChecks simulates writes before sending. RPC failures are classified by classifyRPCError.

Advanced modules

  • State persistence: stateBackend / statePersistDir (JSONFileBackend), saveState(), loadState(), enableAutoSave(ms), disableAutoSave(), validatePersistedState, CURRENT_STATE_VERSION.
  • Order splitting: orderSplitter (OrderSplitter, DEFAULT_ORDER_SPLITTER_CONFIG) chunks large curve buys by curve liquidity.
  • Transaction queue: agent.txQueue (TransactionQueue, executeWithRetry, dead-letter queue helpers) serializes every write.
  • Dry-run: ops.dryRun returns DryRunResult (isDryRunResult, createDryRunResult).
  • Multicall3: multicallBatch; bigint-safe JSON: safeJsonStringify, jsonReplacer; provider abstraction: EthersProviderAdapter, EthersSignerAdapter; audit: auditLog, initAuditSession, generateCorrelationId.

Sub-path exports

@frontierfun/sdk (everything), /abis (generated ABIs), /config, /chains, /v4, /launch, /tools, /strategies, /copytrader, /wallet, /tx-gate.

Environment variables

The SDK reads FRONTIER_PRIVATE_KEY and FRONTIER_KEYSTORE_PASSWORD (fallbacks PRIVATE_KEY, KEYSTORE_PASSWORD) through WalletManager. Everything else (FRONTIER_CHAIN_ID, FRONTIER_RPC_URL, FRONTIER_API_BASE_URL, FRONTIER_EXPLORER_URL, FRONTIER_REFERRAL_BASE_URL, FRONTIER_REFERRER_ADDRESS, the FRONTIER_*_ADDRESS overrides, the TxGate knobs) is read by the MCP server and the OpenClaw plugin and passed into ConnectionConfig; see .env.example in this package for the list.

Networks

| Chain | Id | Default RPC | WebSocket | Explorer | API | |-------|----|-------------|-----------|----------|-----| | Robinhood Chain (production, default) | 4663 | https://rpc.mainnet.chain.robinhood.com | wss://robinhood-rpc.publicnode.com | https://robinscan.io | https://api.frontier.fun | | Arbitrum Sepolia (testnet) | 421614 | https://sepolia-rollup.arbitrum.io/rpc | wss://arbitrum-sepolia-rpc.publicnode.com | https://sepolia.arbiscan.io | https://dev-sepolia.api.frontier.fun |

https://robinhood-rpc.publicnode.com refuses eth_getLogs, which breaks event polling and position enumeration; the SDK defaults to the official RPC over HTTP. Contract addresses live in chains.ts (verified on-chain on 2026-08-17).

Changes

See CHANGELOG.md. Latest: v1.1.0, the official extension set made operable (recipient state, the permissionless calls that drive a lottery / buyback / splitter, and the pool fee schedules). 84 tools.

Development

# From the monorepo root
pnpm install
pnpm build:sdk
pnpm typecheck
npx vitest run --project sdk          # unit tests
pnpm test:integration                  # Anvil fork of Robinhood Chain (needs Foundry)

License

MIT