跳到主要内容

集成

链Robinhood Chain 主网 4663(RPC https://rpc.mainnet.chain.robinhood.com)
浏览器robinhoodchain.blockscout.com
发射项目的曲线和池子以股票报价;池子是共用 hook 上的 Uniswap v4,费率 0.20%,tick spacing 60
金库的股票腿上架指定的股票/USDG Uniswap V3 池,带 600 秒 TWAP
股票预言机Chainlink,RHxxx / USD ÷ USDG 价格源,规则同三种价格
出块时间约 0.1 秒;所有窗口都按秒计

发射代币​

一次发射是一个 Request 加 terms,terms 是 predict(request) 返回的哈希。先登记创建者的选择,再报价,再按同样的 terms 发射。

请求​

字段含义
name、symbol代币的名称和符号
stock已上架的股票;否则 predict 回滚且不返回错误原因
creator永久收取已收税款的 creatorBps,并可否决收款地址提案。用 EOA 或 Safe
taxBps100 到 1500
creatorBps0 到 5000,按已收税款计(maxCreatorBps)。发射表单显示基础税加一个不超过它的发币人税,发送 taxBps = 基础 + 发币人、creatorBps = 发币人 ÷ 合计
tp1Bps、tp2Bps、dipBps、stopBps、lotBps规则数字;含义和范围随策略不同
bandBpsPerHour过期容忍带,0 到 bandCeiling(stock)
nonce区分同一创建者同一符号的两次发射;salt 的一部分
maxFee发射者愿意支付的发射费上限
expectedOpenPriceE18发射者看到的上架开盘价

报价之前:创建者的登记​

四项登记都以 keccak256(abi.encode(symbol, msg.sender, nonce)) 为键,与 factory 从 (symbol, creator, nonce) 推导的 salt 相同, 其他地址登记的内容不会作用于这次发射。

curveDeployer.setCurveConfig(symbol, nonce, 7931, snipeSeconds) // 窗口 0..180 秒;saleBps 必须是 DEFAULT_SALE_BPS
curveDeployer.setOpeningTaxExemptions(symbol, nonce, recipients) // 最多 40 个;创建者不登记也免加价
treasuryDeployer.setStrategyKind(symbol, nonce, kind) // 1..6;类型 0 不用调
treasuryDeployer.setEngineConfig(symbol, nonce, kind, config) // 只有类型 2、3 和 6 需要

登记在发射前可以修改,不能删除。predict 之后改动任何一项,发射会以 Restated 回滚;将报价时的值重新登记一次即可恢复。

报价与发射​

(token, treasury, terms) = factory.predict(request)
id = factory.launch{value: launchFeeAmount}(request, terms)
id = factory.launchWithMetadata{value: launchFeeAmount}(request, terms, metadata)

发布默认值里 launchFeeCurrency 是 Native,launchFeeAmount 是 0.0005 ETH;以 getDefaults() 为准。publicLaunch() 必须为 true,或者调用者是已授权的 launcher(否则 NotOpen)。predict 和 launch 都会执行 deployer 的校验,因此会在 launch 失败的 请求在 predict 时就被具名错误拒绝:BadCurveConfig、BadEngineConfig、StopInsideExecutionFriction、Unseedable。循环 (类型 5)和预留现金的分批止盈(类型 6)是例外:它们的档位下限(tp1Bps 和 dipBps 至少 2 × (maxSlippageBps + 池费率))只在构造函数里检查(类型 6 的 reserveBps 超过 50% 也一样),低于下限的 报价仍能获得 terms,发射时以 TreasuryDeployFailed 回滚。报价前应先套用这个下限。

签名前应向创建者展示上架检查的结论:股票池无法承接的募资永远不能毕业,链上没有任何东西会拒绝这样的发射。

一次 ETH 付款:发射并首买​

launchNativeRouter.launchAndBuy{value: launchFeeAmount + b.amountIn}(request, terms, metadata, b, path)

b 是 (amountIn, minStockReceived, minFinalOut, deadline, allowPartialFill);path 是 WETH 到股票的 V3 路线(通常是 WETH → USDG → 股票)。router 将费用支付给 factory,把剩余的 ETH 包装后沿路线换成股票,再替 request.creator 在新曲线上买入,所以 创建者的开盘加价免除生效,基础税照缴。报价过期、路线错误、买入失败或费用转账失败,整笔一起回滚。owner 须先用 setLauncher(router, true) 授权这个 router。不要用 v1 的 HedgeFunLaunchRouter 发射 v2:它的首买直接进入 v4。

资料和图标​

launchWithMetadata 和 launchAndBuy 在发射交易里写入代币的第一份资料,所以代币上线时资料不会是空的。资料是 (logo, description, (twitter, telegram, discord, website, farcaster), extraURI):每个链接最多 256 字节,介绍最多 1,024 字节 (否则 TooLong)。

网站只显示托管在本站的 logo,即 https://hedgefun.trade/media/icons/<sha256>.png。指向其他地方的 logo 会留在链上,但不显示: 第三方主机能看到每个访客,也能在发射审核之后换掉图片。社交链接只在是对应平台自己域名的 https 链接时显示(x.com 或 twitter.com、t.me、discord.gg 或 discord.com、farcaster.xyz 或 warpcast.com)。

获取托管图标有两种方式:

  • 手动: 在 hedgefun.trade/icons 上传,复制得到的链接。不连接钱包。

  • 脚本: 向 POST /api/icons 发送 PNG,用一个在这条链上持有不少于发射费 ETH 的钱包签名。签名的文字是

    Hedgefun icon upload
    Host: hedgefun.trade
    SHA-256: <PNG 原始字节的 SHA-256,小写十六进制>
    Issued: <Unix 秒,与服务器时间相差不超过 300 秒>
import { readFile } from 'node:fs/promises';
import { createHash } from 'node:crypto';
import { privateKeyToAccount } from 'viem/accounts';

const png = await readFile('icon.png');
const sha256 = createHash('sha256').update(png).digest('hex');
const issued = Math.floor(Date.now() / 1000);
const creator = privateKeyToAccount(process.env.CREATOR_KEY as `0x${string}`);
const signature = await creator.signMessage({
message: `Hedgefun icon upload\nHost: hedgefun.trade\nSHA-256: ${sha256}\nIssued: ${issued}`,
});
const response = await fetch('https://hedgefun.trade/api/icons', {
method: 'POST',
headers: { 'content-type': 'image/png', 'x-icon-issued': String(issued), 'x-icon-signature': signature },
body: png,
});
const { url, error } = await response.json(); // url: https://hedgefun.trade/media/icons/<sha256>.png

PNG 必须是 8 位 RGB 或 RGBA、非隔行、最大 512 × 512 像素和 512 KiB,不含文字、色彩配置或动画块。 sharp(input).resize(512, 512, { fit: 'inside', withoutEnlargement: true }).ensureAlpha().png() 生成的文件即符合要求。 只接受 EOA 签名。同一张图上传两次,得到同一个链接。

发射之后,创建者(代币的 deployer)可以用 token.setMetadata(logo, description, socials, extraURI) 整份替换资料,也可以用 setEditor(editor) 指定另一个地址代为修改(传 address(0) 撤销),适合替创建者发射的机器人。lock() 永久冻结资料;指定了 editor 时会被拒绝,因此应先撤销 editor、核对页面,再锁定。代币页上有给创建者用的编辑器,用钱包做同样的事。

实时值的展示​

募资额、LP 份额和金库份额来自上架的 openPriceE18、curve deployer 的 DEFAULT_SALE_BPS()、供应量、treasury deployer 的 lpBps(stock) 和预言机价格。目标在发射时以股票单位固定;美元数字只是估计。

买入与卖出​

任意支付资产用 HedgeFunV2TradeRouter,ETH 用 HedgeFunV2NativeRouter,持有股票时可以直接调用曲线和池子。

trade router​

buy(params, path) 和 sell(params, path) 接收一个 TradeParams 和一个 Hop[];buyFor(params, path, to) 把代币交给另一个 地址。

字段含义
id这个 factory 上的发射 id
asset买入:付款 ERC20;卖出:期望的产出 ERC20
amountIn买入:付款资产的原始单位;卖出:策略代币数量
minStockReceived买入:付款路线至少应换出的股票,包括之后退回的股票;卖出时忽略
minFinalOut经过所有交换和税之后的最低最终产出,部分成交时不会按比例缩小
deadline允许执行的最后时间戳
expectedStage0 Active 或 2 Graduated,资金移动前检查
allowPartialFill接受买入时的股票退款,或卖出时的代币退款
Hop.pool、Hop.tokenOut下一跳标准 V3 池及其产出资产;最多 3 跳;直接用股票交易时留空

买入路线是 付款 → V3 跳 → 股票 → 曲线或 v4 池 → 代币,卖出反向,以选定的产出资产结束。路线必须连续、资产不重复、池子必须由 factory 指定的 V3 factory 创建;策略代币不能出现在转换路线里。每一跳 V3 都必须完全成交。转账收费和弹性供应的资产被拒绝。任何 失败都回滚整条路线。

退款是股票。 触顶的最后一笔曲线买入、整数取整或 v4 价格限制都可能留下未用完的输入;此时付款已经换成股票,所以未用部分以 股票退回,记在 Bought 事件的 stockRefund 里。即使只是粉尘也需要 allowPartialFill。一笔买入完成毕业后,不会自动在新池 中继续买入。部分成交的 v4 卖出把未卖出的代币记在 tokenRefund 里退回。

买入时两个下限均应设置。minFinalOut 保护代币产出;minStockReceived 保护付款到股票这一腿,因为上游汇率变差时可能仅缩小退款, 而代币产出不变。minStockReceived 为 0 即不保护这一腿。

native router​

HedgeFunV2NativeRouter.buy{value: amountIn}(params, path) 把 msg.value 全额包装,把代币转给调用者;params.asset 和路线 里写的是包装后的 ERC20。sell 解包报价产出,未卖出的代币另行退回。股票退款仍是股票。原生币付出被拒绝时整笔交易回滚。

直接交易​

  • 曲线:先将股票或代币授权给曲线,再调用 buy(stockIn, minTokensOut, recipient, deadline) 或 sell(tokenIn, minStockOut, recipient, deadline)。
  • 池子:用金库保存的 pool key(poolKey()),通过任何 v4 router 做交换。用最低产出限定价格。在 sqrtPriceLimitX96 处停止的 指定输入买入会回滚;指定输出的卖出被拒绝。细节在 Uniswap v4 池。

获取报价​

曲线阶段​

curve.quoteBuy(maxStockIn) → (stockSpent, tokensOut, taxTokens)
curve.quoteBuyFor(maxStockIn, to) → 同上,针对可能免加价的收款人
curve.quoteSell(tokenIn) → (stockOut, taxStock)
curve.buyRateBps(), buyRateBpsFor(to) → 实时买入费率,含开盘加价

stockSpent 是含基础税的总付款;taxTokens 只是开盘加价的销毁量。基础股票费为 floor(stockSpent × taxBps / 10000)。基础费 和加价销毁要分两行展示。会越过毕业目标的买入有上限:stockSpent 小于 maxStockIn,差额以股票退回。在 Active 曲线上获取的 报价,若交易落地时曲线已毕业,则以 StageChanged 回滚;需重新报价。

池子阶段​

hook 在交换前收取买入税,因此仅凭池价无法给出报价。用实际的 router 调用做模拟(从已注资、已授权的调用者,带预期的 deadline、 阶段和输入做 eth_call),展示它返回的产出。买家在毕业池上的成本是税(1% 到 15%)加上按净额计的 0.20% 池子手续费,都以 股票计;最低税率时约 1.2%。毕业池没有开盘窗口,也没有卖出尖峰;hook.buyRateBps(poolId) 和 hook.sellRateBps(poolId) 都返回 固定税率。

经路由报价​

多跳路线先在链下找出 V3 池,再带预期路线模拟 router 调用。展示净产出、两个下限、每项税费,以及退款的币种(股票)。模拟里的 退款金额是估计,不保证最终未用完的输入数量。

换算成 USDG​

池子里的价格是代币以股票计的价格。要显示美元价格或估值,乘以股票的预言机价:

price_usdg = price_in_stock × oracle_usdg(stock)
FDV = price_usdg × supply 说明用的是哪个供应量:已发行,或已发行减已销毁

代币顺序从池子 key 读(vault.poolKey(),或 treasury.stockIsCurrency0InTokenPool()),这条链上各个池的顺序并不一致。开盘 估值以股票计:供应量 × openPriceE18。未读取到的数字显示“未获取”,永远不显示 0。

读取状态​

发射项目​

factory.strategyCount() → 发射总数
factory.strategies(id) → (token, treasury, hook, stock, creator)
factory.curves(id) → 曲线
curve.status() → 0 Active · 1 Ready(瞬时)· 2 Graduated
curve.realStockReserve() / curve.terminalStock() → 毕业进度
curve.tokenReserve(), curve.minTokenReserve() → 未售库存与销售终点
hook.poolOfTreasury(treasury) → 毕业后的 v4 池 id
hook.liquidityVaultOf(poolId) → vault
treasuryDeployer.strategyKindOf(salt) → 类型;lpBpsOfTreasury(treasury) 是冻结的 LP 份额

毕业进度是 realStockReserve / terminalStock。目标在发射时以原始股票单位固定(CurveLaunched 带 saleBps 和 virtualStock)。

池子​

vault.poolKey() 给出 key;vault.seeded()、surplusLiquidity()、surplusTickLower() 和 surplusTickUpper() 描述两个头寸; vault.lockedSeedTokens() 是取整剩余。一个池子的股票储备是 vault 的两个头寸加未领取的手续费,从池子自身状态读; balanceOf(PoolManager) 是这条链上所有池的总和。hook.rates(poolId) 返回冻结的费率;hook.accrued(poolId) 和 owedProtocol、owedCreator、owedTreasury 是等待 sweep 的费用。

金库​

视图类型含义
health()全部(ok, price):股票腿现在能否交易。毕业前为 (false, 0)
unbookedStock()全部已到账、尚未记账的股票;任何人可调 book()
buybackStock()全部等着回购代币的股票
totalBurned()、totalStockSpentOnBuybacks()全部回购记录
params()全部冻结的规则数字和边界
lotCount()、lots(id)、reserveUsdg()、lastSalePrice()、lastStopAt()分批止盈、按比例回购、循环lot 账本和回调参考价
protectedGraduationStock()回购销毁闲置的毕业资本
engineConfig()、preview()、avgCost()固定仓位、再平衡冻结的字段、下一次 execute() 会做什么、库存平均成本
riskLimits()再平衡实时可交易价值、单次上限、当日上限和剩余额度
implementation()、upgradeConfigHash()、treasuryUpgradeController()全部代理当前的逻辑和身份
controller.proposals(treasury)全部待执行的升级:实现、epoch、代码哈希、数据哈希、readyAt

把一个发射项目显示为可交易之前先展示 health()。代币页上要显示待执行的升级提案,这是已发射项目中唯一可能变化的部分。

keeper 循环​

for id in 0 .. factory.strategyCount()-1:
(token, treasury, hook, stock, creator) = factory.strategies(id)
if factory.curves(id).status() != 2: continue # 曲线阶段只有 claimFees 可做
(ok, p) = treasury.health()
分批止盈一族: 模拟 treasury.execute();不回滚 NotDue 就发送
回购销毁: 模拟 treasury.buyback()
固定仓位、再平衡:(due, action, amountIn) = treasury.preview();模拟 execute()
所有类型: buybackStock() > 0 且冷却已过时,模拟 treasury.buyback()
hook.accrued(poolId) 非零时调 hook.sweep(poolId);手续费有增长时调 vault.collectFees()

NotDue 是正常答复,eth_call 不产生费用。execute() 运行时会顺带记账待处理股票,以 NotDue 结束时会将记账回滚,因此有股票待记账、 而没有动作到期时,应单独调用 book()。报酬是动作产出的 0.1%,以产出的资产支付;sweep 和 collectFees 没有报酬。

领取费用与 sweep​

费用不会自动移动。以下三个调用负责移动费用,任何人均可发起。

调用合约移动什么给谁调用者报酬事件
claimFees(recipient)曲线该收款方累计的税,以股票计只给该收款方没有FeesClaimed(recipient, amount)
sweep(poolId)hook这个池子买方和卖方的全部费用 claim,以股票计协议、创建者、金库没有(sweepTipBps 为 0)Swept(id, tokenBurned, stockToTreasury, stockToProtocol, stockToCreator)
collectFees()vault两个头寸累计的池子手续费股票进金库的 buybackStock;代币销毁没有FeesCollected(stockToTreasury, tokenBurned)

claimFees​

曲线阶段和毕业后都可用。claimable(recipient) 给出待领金额。被股票发行方拦截的收款方不会阻碍另外两方的领取,也不会阻碍曲线交易; 其 claim 保持待领状态。

sweep​

hook 把费用记为 ERC-6909 股票 claim,每个池一本账,所有池在同一个地址,所以金额来自 accrued(poolId),从不来自余额。每种货币 各走自己的一腿;一腿无法结算时不会阻碍另一腿。vault 池上 tokenBurned 为 0:从来没有费用以代币形式持有。sweep 不记账金库的 份额;book() 或下一次 execute() 才记账。

collectFees​

对两个头寸执行零流动性变动的操作,无法动用本金。股票转给金库被发行方拒绝时,vault 把股票留作待交付,creditPendingStock() 或 下一次 collectFees() 重试;代币销毁照常完成。v4 的 take 内部被拒绝的股票转账会让这次领取回滚。

金库的调用​

book() 按当时价格给收入记账;execute() 和 buyback() 向调用者支付产出的 0.1%。keeper 循环在读取状态。