Vaults
一个 tax token 会从每笔买入和卖出中收取一部分。这一部分会流向一个 fee receiver。在普通的 tax-token 上线中,fee receiver 是一个钱包。在 vault 上线中,fee receiver 是一个在上线时 部署的合约,因此收入会落到一个带规则的地方:把它拆分给多个钱包、把它流式支付给单个接收方、 为某个身份代管,或者 vault 作者所编写的其他任何逻辑。
本页面向想要用 vault 上线或读取 vault 的集成方。如果你想编写自己的 vault,参见 custom-vault.md。
目录
收入如何到达 vault
trader ──buy/sell──▶ tax token
│ collects tax on the transfer
▼
settles the tax ← permissionless; anyone can poke it
│ splits by the allocation you set at launch
┌──────────────┼───────────────┬───────────────┐
▼ ▼ ▼ ▼
fee receiver holders burn liquidity
(the vault) (dividends) (0xdead) (adds LP)
代币并不在每笔交易上推送收入。它累计 tax 并分批结算。结算时,fee receiver 份额会被发送到
vault。你的 vault 记录这笔收入,稍后通过它自己的 distribute() / claim() 调用支付出去。
正是这种分批机制,使得 vault 总是呈现"现在累计、稍后支付"的形态。
CheesePad 对 creator tax 收取的 10% 平台费是一笔独立的费用,不属于你的 allocation;关于它 如何与 tax 并存,参见代币费用模型。
四个 allocation 份额来自你在上线时于 taxConfig 中设置的 allocation,它们之和必须为
10000 BPS:
| Slice | Where it goes |
|---|---|
feeReceiver | the vault |
holders | token holders, as dividends (needs a non-zero minHolderShare) |
burn | the dead address |
liquidity | added back as LP |
要让一个 vault 有机会获得支付,allocation.feeReceiver 必须大于零,且至少一侧 tax 必须非零。
launchpad 会检查这一点,否则会 revert 掉这次上线。
用 vault 上线
使用 createMicroSaleTaxTokenWithVaultV2。在通常的 CreateParamsV2 之外,你还要传入一个
vault 工厂地址、一个用于配置具体 vault 的 ABI 编码 vaultData 数据块,以及指定要上线的 DEX
的 dexKey。
function createMicroSaleTaxTokenWithVaultV2(
CreateParamsV2 calldata params, // same struct as the tax-token launch, incl. taxConfig
address vaultFactory, // a factory the platform trusts (see Factory addresses)
bytes calldata vaultData, // ABI-encoded config for this vault; shape depends on factory
address dexKey // the DEX to list on
) external payable returns (address fundManager);
launchpad 会按顺序做以下这些事,全部为 fail-closed(任何一步失败都会 revert 整个上线):
- 确认工厂指向这个 launchpad。
- 确认工厂接受该销售的 quote token。
- 确认工厂理解当前的 launch-data 版本。
- 用你的上线参数调用工厂的 review hook;它必须批准。
- 让工厂部署 vault。
- 确认 vault 的 revenue 模式与 quote token 匹配(见下文)。
- 把 vault 绑定为代币的 fee receiver。
你从不需要自己接线任何东西。如果工厂批准,你就会得到一个活跃的代币,其 tax 会流入一个全新的 vault。
同样的握手流程支撑着 bonding curve tax token:createBondingCurveTaxTokenWithVaultV2 接受一个
vaultFactory、vaultData 和 dexKey,并运行完全相同的检查。约束在于 vault 的 revenue 模式
必须与曲线的 quote currency 匹配——BNB 曲线用 native 模式,以 ERC20 计价的曲线用 ERC20 模式
(见原生 vs ERC20 收入)。下文的一切对两种上线方式都适用。
编码 vaultData
vaultData 对 launchpad 而言是不透明的;每个工厂定义自己的形态。工厂通过 vaultDataSchema()
公布这个形态,因此 UI 可以通用地构建表单。若要手动编码,请遵循 schema 的字段顺序与类型。两个
现成 vault 的具体示例见下文。
取回 vault 地址
上线之后,从销售中读取它:
const sale = await launchpad.getMicroSaleByFundManager(fundManager);
console.log("vault:", sale.vault); // the deployed vault
console.log("via factory:", sale.vaultFactory); // the factory that produced it
销售还会记录是哪个工厂生产了这个 vault,因此一个链下白名单可以为来自可信工厂的 vault 打上 标记。
原生 vs ERC20 收入
一个 vault 只处理一种资产的收入,在上线时决定:
- Native 模式:当销售的 quote token 是 WBNB 时。收入以 BNB 到达。
- ERC20 模式:其他情况。收入以 quote token 到达,且孤立的原生币会被拒收。
launchpad 会断言 vault 的模式与 quote token 匹配,因此一个不匹配的工厂无法使交易瘫痪。对于以 ERC20 计价的上线,贡献者也会以那个 ERC20 quote token 募资—— 参见 microsale.md。quote token 来自模板的绑定,因此针对一个绑定了 ERC20 的模板发起 wallet-receiver 或 no-tax 上线时,也会以那个 quote token 募资;vault 带来的差别 仅在于 fee-receiver 份额落到哪里。
CheesePad 提供的 vault
两个现成的 vault,各自有自己的工厂。你把 createMicroSaleTaxTokenWithVaultV2 指向该工厂,并
传入匹配的 vaultData。
SplitterVault:按份额固定分给收款人
按基点把收入拆分给一个固定的钱包列表。最多 50 个收款人,份额之和必须为 10000,不得重复,不得 有零地址。
vaultData 为 abi.encode(address[] payees, uint16[] bps):
const payees = ["0xAaa...", "0xBbb..."];
const bps = [7000, 3000]; // 70% / 30%; must sum to 10000
const vaultData = ethers.AbiCoder.defaultAbiCoder().encode(
["address[]", "uint16[]"],
[payees, bps]
);
await launchpadRW.createMicroSaleTaxTokenWithVaultV2(params, SPLITTER_FACTORY, vaultData, dexKey, {
value: creationFee,
});
上线后使用该 vault:
distribute()是无需许可的。它把已累计余额中每个收款人的份额记入其名下并推送给他们。如果 某个收款人无法接收(例如一个会 revert 的合约),他的份额会被保留为owed,供其稍后自行 提取,其他人照常获得支付。claim()让某个收款人提取自己的owed余额。splitConfig()返回收款人及其 bps;owed(address)返回某个收款人的待领余额;credited()返回已记入的累计总额。
SplitterVault 接受任何 quote token。
GiftVault:流式支付给某个 X 账号背后的钱包
为某个 X(Twitter)账号背后的人代管一个代币的收入。在一个钱包被绑定到该账号之前,没有人会 获得支付,而绑定由一个平台 relayer 签名授权,因此只有该账号的真正拥有者才能领取。
vaultData 为 abi.encode(string xHandle, uint256 timeoutPeriod):
const vaultData = ethers.AbiCoder.defaultAbiCoder().encode(
["string", "uint256"],
["cheesepad", 30n * 24n * 60n * 60n] // handle, and 30-day bind window
);
await launchpadRW.createMicroSaleTaxTokenWithVaultV2(params, GIFT_FACTORY, vaultData, dexKey, {
value: creationFee,
});
timeoutPeriod 介于 1 到 365 天之间。GiftVault 工厂还要求 allocation.holders 大于零,因为
存在下面的回退机制。
它的运作过程:
- Bind(绑定)。 钱包拥有者取得一个由 relayer 签名的证明(一个 EIP-712 签名,覆盖账号、
代币、受益人钱包、一个 nonce 和一个过期时间),并调用
bindBeneficiary(beneficiary, nonce, validUntil, signature)。从此之后,收入会流式支付给那个 钱包。绑定只在超时之前开放,且只有在证明校验通过后才可进行。 - Withdraw(提取)。 绑定之后,
distribute()会把已累计的收入推送给受益人,claim()则 让他们自行提取。 - Lock(锁定)。 受益人可以调用
lock(),将 relayer 和平台从任何未来的重新绑定中排除。 - Rescue(救援)。 在超时之前且在锁定之前,平台后备机制可以纠正一个被错误绑定的钱包。它 只会重定向未来的收入。
- Dividend fallback(分红回退)。 如果在超时之前无人绑定,任何人都可以调用
distributeDividend(),它会把未领取的收入作为分红路由给代币的持有者。这就是工厂坚持要有一个 holder 份额的原因。一旦它触发,vault 就永久进入分红模式,绑定随之关闭。
通用地读取一个 vault
每个 vault 都通过 vaultUISchema() 描述自己的读取字段与操作。一个 UI 可以渲染一个它从未见过
的 vault:它读取 schema,调用每个读取方法,并用声明的类型解码结果。通用形态:
const vault = new ethers.Contract(sale.vault, VaultABI, provider);
const isNative = await vault.isNativeRevenue(); // true = BNB, false = ERC20
const quote = await vault.quoteToken(); // revenue asset
const desc = await vault.description(); // one-liner for listings
const schema = await vault.vaultUISchema(); // methods to render (reads + writes)
vaultUISchema() 返回一个 vaultType、一个 description 和一个方法列表。每个方法要么是一个
读取字段(UI 调用它并展示带类型的结果),要么是一个写入操作(UI 渲染一个按钮和一个输入表单,
外加它事先需要的任何 ERC20 approval)。schema 类型在
custom-vault.md 和
events-and-data.md 中有说明。
对于通用读取方,重要之处在于:一个 vault 只会暴露它的 schema 所声明的内容。没有自动的存储 自省,所以请把 schema 当作一切值得展示的内容的清单来信任。
工厂地址
| Vault | Network | Factory |
|---|---|---|
| SplitterVault | BNB (56) | 0x9d04B0Bfe281411d4A01a69BD89f7Ba6b7F5A59b |
| SplitterVault | BNB testnet (97) | 0xbB5165684832a8Cda1121081EBb168ca6636abc6 |
| SplitterVault | Robinhood (4663) | 0x58DBab8C3529492c12Ee913313Cf600f522a39D9 |
| GiftVault | BNB (56) | 0xFa36D3e2240BEDB22bad35D8FD4165AAf0598896 |
| GiftVault | BNB testnet (97) | 0x8227F4330E3aa43c94b9fDc409D71A6C6C2033B7 |
| GiftVault | Robinhood (4663) | 0x7FD34AeA57250aC66C59448339316aF745f6B9ef |
工厂是无状态且不可变的。新版本是一个新地址,所以在你针对它自动化上线之前,请先向 CheesePad 团队确认当前地址。