|
1 | 1 | const ADDRESSES = require('../helper/coreAssets.json') |
2 | 2 | const { getLogs2 } = require('../helper/cache/getLogs') |
| 3 | +const { sumTokens2 } = require('../helper/unwrapLPs') |
3 | 4 |
|
4 | | -// Frontier (frontier.fun) — bonding-curve token launchpad on Robinhood Chain (4663). |
5 | | -// Each launched token custodies the native ETH its own curve raises, so TVL is the |
6 | | -// ETH sitting in un-graduated token contracts. When a curve fills, the token pays |
7 | | -// the creator fee and seeds a Uniswap V4 pool in the same transaction, leaving its |
8 | | -// own balance at zero — graduated markets therefore drop out on their own, and |
9 | | -// their liquidity is already counted by the uniswap-v4 adapter on this chain. |
10 | | -const FACTORY = '0x3cbC9395046607C083B383DC3588A3e8308dFf54' |
11 | | -const FROM_BLOCK = 23650298 // block of the first CoinDeployed event |
| 5 | +// Frontier (frontier.fun) — token launchpad on Robinhood Chain (4663), two deployments. |
| 6 | +// |
| 7 | +// tvl: |
| 8 | +// - Curve ETH. Each un-graduated token custodies the native ETH its own curve raises |
| 9 | +// (verified: reserveBalance - VIRTUAL_BALANCE equals the token's ETH balance to the |
| 10 | +// wei). When the curve fills, the token pays the creator fee and seeds a Uniswap V4 |
| 11 | +// pool in the same transaction, leaving its balance at zero. v1.2 direct-seed |
| 12 | +// launches skip the curve and never hold ETH. |
| 13 | +// - Locked seed liquidity (v1.2). Every pool's seed position NFT is minted straight to |
| 14 | +// the Harvester, which can only collect fees from it — the liquidity is locked for |
| 15 | +// good. Counted as the ETH leg of those positions; the coin leg is the launched |
| 16 | +// token's own unsold supply and is not counted. This liquidity is also in the |
| 17 | +// uniswap-v4 adapter's TVL on this chain, hence `doublecounted`. |
| 18 | +// |
| 19 | +// Frontier's ERC-4626 StakingVaults are excluded as the staked assets are launched tokens |
| 20 | +const V1_FACTORY = '0x3cbC9395046607C083B383DC3588A3e8308dFf54' |
| 21 | +const V1_FROM_BLOCK = 23650298 // first v1 CoinDeployed |
| 22 | +const V12_FACTORY = '0xe3A826C056e578c240D362BF4C2fa53E5c0c17a5' |
| 23 | +const V12_FROM_BLOCK = 36671438 // v1.2 deploy block |
| 24 | +const HARVESTER = '0x2F33cb57fAa8bF1EB52ea18D90B0dc2f8cc2Db1f' |
| 25 | +const POSITION_MANAGER = '0x58daec3116aae6d93017baaea7749052e8a04fa7' // Uniswap V4 posm |
12 | 26 |
|
13 | | -const COIN_DEPLOYED_EVENT = 'event CoinDeployed(address indexed creator, address indexed token, address factory, address lp, string name, string symbol, string description, string image, uint256 initialSupply, uint256 maxSupply, uint256 initialETHReserves, uint256 initialPrice, uint256 initialMarketCap, uint256 targetETH)' |
| 27 | +const V1_COIN_DEPLOYED_EVENT = 'event CoinDeployed(address indexed creator, address indexed token, address factory, address lp, string name, string symbol, string description, string image, uint256 initialSupply, uint256 maxSupply, uint256 initialETHReserves, uint256 initialPrice, uint256 initialMarketCap, uint256 targetETH)' |
| 28 | +const V12_COIN_DEPLOYED_EVENT = 'event CoinDeployed(address indexed creator, address indexed token, address factory, address lp, string name, string symbol, string description, string image, uint256 initialSupply, uint256 maxSupply, uint256 initialETHReserves, uint256 initialPrice, uint256 initialMarketCap, uint256 targetETH, bool indexed directSeed)' |
| 29 | +const NFT_TRANSFER_EVENT = 'event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)' |
| 30 | +const NFT_TRANSFER_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' |
| 31 | +const asTopic = (address) => '0x' + address.slice(2).toLowerCase().padStart(64, '0') |
14 | 32 |
|
| 33 | +/** |
| 34 | + * Native ETH custodied by un-graduated bonding-curve tokens (v1 + v1.2 |
| 35 | + * factories, enumerated from CoinDeployed; direct-seed launches never hold |
| 36 | + * ETH), plus the ETH leg of the permanently locked Uniswap V4 seed positions |
| 37 | + * held by the Harvester. The coin leg of those positions is the launched |
| 38 | + * token's own unsold supply and is not counted. |
| 39 | + * @param {object} api - DefiLlama chain api for the block being measured |
| 40 | + */ |
15 | 41 | async function tvl(api) { |
16 | | - const logs = await getLogs2({ api, factory: FACTORY, eventAbi: COIN_DEPLOYED_EVENT, fromBlock: FROM_BLOCK }) |
17 | | - return api.sumTokens({ tokensAndOwners: logs.map(log => [ADDRESSES.null, log.token]) }) |
| 42 | + const [v1Launches, v12Launches, positionTransfers] = await Promise.all([ |
| 43 | + getLogs2({ api, factory: V1_FACTORY, eventAbi: V1_COIN_DEPLOYED_EVENT, fromBlock: V1_FROM_BLOCK }), |
| 44 | + getLogs2({ api, factory: V12_FACTORY, eventAbi: V12_COIN_DEPLOYED_EVENT, fromBlock: V12_FROM_BLOCK }), |
| 45 | + // Seed positions are minted to the Harvester and never leave it, so every |
| 46 | + // position NFT ever sent there is one it still holds; ownerOf below is only |
| 47 | + // a guard against that assumption breaking. |
| 48 | + getLogs2({ |
| 49 | + api, |
| 50 | + target: POSITION_MANAGER, |
| 51 | + eventAbi: NFT_TRANSFER_EVENT, |
| 52 | + topics: [NFT_TRANSFER_TOPIC, null, asTopic(HARVESTER)], |
| 53 | + fromBlock: V12_FROM_BLOCK, |
| 54 | + extraKey: 'harvester-positions', |
| 55 | + }), |
| 56 | + ]) |
| 57 | + |
| 58 | + const curveTokens = [ |
| 59 | + ...v1Launches.map((log) => log.token), |
| 60 | + ...v12Launches.filter((log) => !log.directSeed).map((log) => log.token), |
| 61 | + ] |
| 62 | + await api.sumTokens({ tokensAndOwners: curveTokens.map((token) => [ADDRESSES.null, token]) }) |
| 63 | + |
| 64 | + const positionIds = [...new Set(positionTransfers.map((log) => log.tokenId.toString()))] |
| 65 | + if (!positionIds.length) return |
| 66 | + const owners = await api.multiCall({ |
| 67 | + target: POSITION_MANAGER, |
| 68 | + abi: 'function ownerOf(uint256 tokenId) view returns (address)', |
| 69 | + calls: positionIds, |
| 70 | + permitFailure: true, |
| 71 | + }) |
| 72 | + const lockedPositionIds = positionIds.filter((_, i) => owners[i]?.toLowerCase() === HARVESTER.toLowerCase()) |
| 73 | + if (!lockedPositionIds.length) return |
| 74 | + |
| 75 | + return sumTokens2({ |
| 76 | + api, |
| 77 | + resolveUniV4: true, |
| 78 | + uniV4ExtraConfig: { |
| 79 | + positionIds: lockedPositionIds, |
| 80 | + whitelistedTokens: [ADDRESSES.null], |
| 81 | + }, |
| 82 | + }) |
18 | 83 | } |
19 | 84 |
|
20 | 85 | module.exports = { |
21 | 86 | methodology: |
22 | | - 'Counts the native ETH held by Frontier bonding-curve token contracts on Robinhood Chain, enumerated from the factory\'s CoinDeployed events. Each token custodies the ETH its own curve raises until the curve fills, at which point the token pays out the creator fee and seeds a Uniswap V4 pool in the same transaction and its balance goes to zero — so graduated markets are excluded here and their liquidity is counted by the Uniswap V4 adapter instead. The launched tokens themselves are not counted.', |
| 87 | + 'TVL is the native ETH held by un-graduated Frontier bonding-curve tokens on Robinhood Chain (v1 and v1.2 factories, enumerated from CoinDeployed; each token custodies what its curve raises until it fills and seeds its Uniswap V4 pool), plus the ETH leg of the permanently locked Uniswap V4 seed positions held by the Harvester (v1.2; the coin leg is the launched token itself and is not counted). Launched tokens are not counted anywhere, including the coins staked in Frontier\'s vaults, since their ETH backing is already counted here.', |
23 | 88 | start: '2026-07-30', |
| 89 | + // The locked seed positions are Uniswap V4 liquidity, already in uniswap-v4's TVL on this chain. |
| 90 | + doublecounted: true, |
24 | 91 | robinhood: { tvl }, |
25 | 92 | } |
0 commit comments