@zoralabs/coins 2.4.1 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.abi-stability +923 -0
- package/.turbo/turbo-build$colon$js.log +143 -129
- package/CHANGELOG.md +38 -16
- package/abis/BaseCoin.json +23 -0
- package/abis/ContentCoin.json +23 -0
- package/abis/CreatorCoin.json +18 -0
- package/abis/ICoin.json +5 -0
- package/abis/ICoinV3.json +5 -0
- package/abis/IHasCreationInfo.json +20 -0
- package/abis/ITrendCoin.json +130 -0
- package/abis/ITrendCoinErrors.json +23 -0
- package/abis/IUniversalRouter.json +61 -0
- package/abis/IZoraFactory.json +227 -0
- package/abis/TrendCoin.json +2043 -0
- package/abis/ZoraFactoryImpl.json +232 -0
- package/dist/index.cjs +962 -117
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +960 -117
- package/dist/index.js.map +1 -1
- package/dist/wagmiGenerated.d.ts +1404 -131
- package/dist/wagmiGenerated.d.ts.map +1 -1
- package/package/wagmiGenerated.ts +970 -119
- package/package.json +4 -2
- package/src/BaseCoin.sol +44 -14
- package/src/ContentCoin.sol +20 -1
- package/src/CreatorCoin.sol +3 -0
- package/src/TrendCoin.sol +117 -0
- package/src/ZoraFactoryImpl.sol +142 -1
- package/src/hooks/ZoraV4CoinHook.sol +73 -8
- package/src/interfaces/ICoin.sol +5 -1
- package/src/interfaces/ICreatorCoin.sol +0 -3
- package/src/interfaces/IHasCreationInfo.sol +12 -0
- package/src/interfaces/IPoolManager.sol +13 -0
- package/src/interfaces/ITrendCoin.sol +26 -0
- package/src/interfaces/ITrendCoinErrors.sol +18 -0
- package/src/interfaces/IZoraFactory.sol +60 -1
- package/src/libs/CoinConstants.sol +25 -1
- package/src/libs/CoinRewardsV4.sol +67 -19
- package/src/libs/CoinSetup.sol +7 -1
- package/src/libs/TickerUtils.sol +84 -0
- package/src/libs/UniV4SwapToCurrency.sol +2 -1
- package/src/libs/V3ToV4SwapLib.sol +7 -3
- package/src/version/ContractVersionBase.sol +1 -1
- package/test/CoinUniV4.t.sol +4 -0
- package/test/ContentCoinRewards.t.sol +1 -0
- package/test/CreatorCoin.t.sol +2 -1
- package/test/CreatorCoinRewards.t.sol +1 -0
- package/test/Factory.t.sol +31 -5
- package/test/LaunchFee.t.sol +284 -0
- package/test/LiquidityMigration.t.sol +0 -2
- package/test/TrendCoin.t.sol +1077 -0
- package/test/Upgrades.t.sol +16 -3
- package/test/utils/FeeEstimatorHook.sol +33 -8
- package/test/utils/V4TestSetup.sol +36 -4
- package/wagmi.config.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zoralabs/coins",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"tsup": "^7.2.0",
|
|
35
35
|
"tsx": "^3.13.0",
|
|
36
36
|
"typescript": "^5.2.2",
|
|
37
|
-
"viem": "
|
|
37
|
+
"viem": "2.22.12",
|
|
38
38
|
"@zoralabs/shared-contracts": "^0.0.5",
|
|
39
39
|
"@zoralabs/shared-scripts": "^0.0.0",
|
|
40
40
|
"@zoralabs/tsconfig": "^0.0.1"
|
|
@@ -49,6 +49,8 @@
|
|
|
49
49
|
"prettier:check": "prettier --check 'src/**/*.sol' 'test/**/*.sol'",
|
|
50
50
|
"prettier:write": "prettier --write 'src/**/*.sol' 'test/**/*.sol'",
|
|
51
51
|
"test": "forge test -vv",
|
|
52
|
+
"abi-check:check": "../../scripts/abi-check.sh check",
|
|
53
|
+
"abi-check:generate": "../../scripts/abi-check.sh generate",
|
|
52
54
|
"test-gas": "forge test --gas-report",
|
|
53
55
|
"update-contract-version": "pnpm exec update-contract-version",
|
|
54
56
|
"wagmi:generate": "pnpm run build:contracts:minimal && wagmi generate && pnpm exec rename-generated-abi-casing ./package/wagmiGenerated.ts"
|
package/src/BaseCoin.sol
CHANGED
|
@@ -11,6 +11,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
|
11
11
|
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
12
12
|
import {ICoin, IHasTotalSupplyForPositions, IHasCoinType} from "./interfaces/ICoin.sol";
|
|
13
13
|
import {IHasRewardsRecipients} from "./interfaces/IHasRewardsRecipients.sol";
|
|
14
|
+
import {IHasCreationInfo} from "./interfaces/IHasCreationInfo.sol";
|
|
14
15
|
import {ICoinComments} from "./interfaces/ICoinComments.sol";
|
|
15
16
|
import {IERC7572} from "./interfaces/IERC7572.sol";
|
|
16
17
|
import {IUniswapV3Factory} from "./interfaces/IUniswapV3Factory.sol";
|
|
@@ -52,7 +53,7 @@ import {PoolState} from "./types/PoolState.sol";
|
|
|
52
53
|
\$$$$$$ | $$$$$$ |$$$$$$\ $$ | \$$ |
|
|
53
54
|
\______/ \______/ \______|\__| \__|
|
|
54
55
|
*/
|
|
55
|
-
abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable, MultiOwnable, ERC165Upgradeable {
|
|
56
|
+
abstract contract BaseCoin is ICoin, IHasCreationInfo, ContractVersionBase, ERC20PermitUpgradeable, MultiOwnable, ERC165Upgradeable {
|
|
56
57
|
using SafeERC20 for IERC20;
|
|
57
58
|
|
|
58
59
|
/// @notice The address of the protocol rewards contract
|
|
@@ -75,8 +76,8 @@ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable
|
|
|
75
76
|
string public tokenURI;
|
|
76
77
|
/// @notice The address of the coin creator
|
|
77
78
|
address public payoutRecipient;
|
|
78
|
-
/// @notice The address of the platform referrer
|
|
79
|
-
address
|
|
79
|
+
/// @notice The address of the platform referrer (internal storage, use platformReferrer() getter)
|
|
80
|
+
address internal _platformReferrer;
|
|
80
81
|
/// @notice The address of the currency
|
|
81
82
|
address public currency;
|
|
82
83
|
|
|
@@ -84,6 +85,11 @@ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable
|
|
|
84
85
|
string private _name;
|
|
85
86
|
/// @notice The symbol of the token
|
|
86
87
|
string private _symbol;
|
|
88
|
+
/// @notice The timestamp when the coin was created
|
|
89
|
+
uint256 private _creationTimestamp;
|
|
90
|
+
|
|
91
|
+
/// @dev Transient storage slot for tracking deployment state
|
|
92
|
+
bytes32 private constant _IS_DEPLOYING_SLOT = keccak256("BaseCoin.isDeploying");
|
|
87
93
|
|
|
88
94
|
/**
|
|
89
95
|
* @notice The constructor for the static Coin contract deployment shared across all Coins.
|
|
@@ -125,6 +131,12 @@ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable
|
|
|
125
131
|
uint160 sqrtPriceX96,
|
|
126
132
|
PoolConfiguration memory poolConfiguration_
|
|
127
133
|
) public virtual initializer {
|
|
134
|
+
// Set transient deploying flag for launch fee bypass
|
|
135
|
+
_setIsDeploying(true);
|
|
136
|
+
|
|
137
|
+
// Record creation timestamp for launch fee calculation
|
|
138
|
+
_creationTimestamp = block.timestamp;
|
|
139
|
+
|
|
128
140
|
currency = currency_;
|
|
129
141
|
// we need to set this before initialization, because
|
|
130
142
|
// distributing currency relies on the poolkey being set since the hooks
|
|
@@ -152,11 +164,6 @@ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable
|
|
|
152
164
|
string memory symbol_,
|
|
153
165
|
address platformReferrer_
|
|
154
166
|
) internal {
|
|
155
|
-
// Validate the creation parameters
|
|
156
|
-
if (payoutRecipient_ == address(0)) {
|
|
157
|
-
revert AddressZero();
|
|
158
|
-
}
|
|
159
|
-
|
|
160
167
|
_setNameAndSymbol(name_, symbol_);
|
|
161
168
|
|
|
162
169
|
// Set base contract state, leave name and symbol empty to save space.
|
|
@@ -167,12 +174,12 @@ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable
|
|
|
167
174
|
|
|
168
175
|
__MultiOwnable_init(owners_);
|
|
169
176
|
|
|
170
|
-
// Set mutable state
|
|
171
|
-
|
|
177
|
+
// Set mutable state (no validation here - subclasses validate if needed)
|
|
178
|
+
payoutRecipient = payoutRecipient_;
|
|
172
179
|
_setContractURI(tokenURI_);
|
|
173
180
|
|
|
174
181
|
// Store the referrer or use the protocol reward recipient if not set
|
|
175
|
-
|
|
182
|
+
_platformReferrer = platformReferrer_ == address(0) ? protocolRewardRecipient : platformReferrer_;
|
|
176
183
|
|
|
177
184
|
// Distribute the initial supply
|
|
178
185
|
_handleInitialDistribution();
|
|
@@ -204,7 +211,7 @@ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable
|
|
|
204
211
|
|
|
205
212
|
/// @notice Set the contract URI
|
|
206
213
|
/// @param newURI The new URI
|
|
207
|
-
function setContractURI(string memory newURI) external onlyOwner {
|
|
214
|
+
function setContractURI(string memory newURI) external virtual onlyOwner {
|
|
208
215
|
_setContractURI(newURI);
|
|
209
216
|
}
|
|
210
217
|
|
|
@@ -220,7 +227,7 @@ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable
|
|
|
220
227
|
return _name;
|
|
221
228
|
}
|
|
222
229
|
|
|
223
|
-
function setNameAndSymbol(string memory newName, string memory newSymbol) external onlyOwner {
|
|
230
|
+
function setNameAndSymbol(string memory newName, string memory newSymbol) external virtual onlyOwner {
|
|
224
231
|
_setNameAndSymbol(newName, newSymbol);
|
|
225
232
|
}
|
|
226
233
|
|
|
@@ -253,7 +260,25 @@ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable
|
|
|
253
260
|
interfaceId == type(IHasPoolKey).interfaceId ||
|
|
254
261
|
interfaceId == type(IHasCoinType).interfaceId ||
|
|
255
262
|
interfaceId == type(IHasTotalSupplyForPositions).interfaceId ||
|
|
256
|
-
interfaceId == type(IHasSwapPath).interfaceId
|
|
263
|
+
interfaceId == type(IHasSwapPath).interfaceId ||
|
|
264
|
+
interfaceId == type(IHasCreationInfo).interfaceId;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/// @inheritdoc IHasCreationInfo
|
|
268
|
+
function creationInfo() external view returns (uint256 creationTimestamp, bool isDeploying) {
|
|
269
|
+
creationTimestamp = _creationTimestamp;
|
|
270
|
+
bytes32 slot = _IS_DEPLOYING_SLOT;
|
|
271
|
+
assembly {
|
|
272
|
+
isDeploying := tload(slot)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/// @dev Sets the transient deploying flag
|
|
277
|
+
function _setIsDeploying(bool value) internal {
|
|
278
|
+
bytes32 slot = _IS_DEPLOYING_SLOT;
|
|
279
|
+
assembly {
|
|
280
|
+
tstore(slot, value)
|
|
281
|
+
}
|
|
257
282
|
}
|
|
258
283
|
|
|
259
284
|
/// @dev Overrides ERC20's _update function to emit a superset `CoinTransfer` event
|
|
@@ -304,6 +329,11 @@ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable
|
|
|
304
329
|
return poolKey.hooks;
|
|
305
330
|
}
|
|
306
331
|
|
|
332
|
+
/// @inheritdoc IHasRewardsRecipients
|
|
333
|
+
function platformReferrer() external view virtual returns (address) {
|
|
334
|
+
return _platformReferrer;
|
|
335
|
+
}
|
|
336
|
+
|
|
307
337
|
/// @notice Migrate liquidity from current hook to a new hook implementation
|
|
308
338
|
/// @param newHook Address of the new hook implementation
|
|
309
339
|
/// @param additionalData Additional data to pass to the new hook during initialization
|
package/src/ContentCoin.sol
CHANGED
|
@@ -10,7 +10,7 @@ pragma solidity ^0.8.23;
|
|
|
10
10
|
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
|
|
11
11
|
import {BaseCoin} from "./BaseCoin.sol";
|
|
12
12
|
import {CoinConstants} from "./libs/CoinConstants.sol";
|
|
13
|
-
import {IHasCoinType} from "./interfaces/ICoin.sol";
|
|
13
|
+
import {IHasCoinType, ICoin, PoolKey, PoolConfiguration} from "./interfaces/ICoin.sol";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* @title ContentCoin
|
|
@@ -31,6 +31,25 @@ contract ContentCoin is BaseCoin {
|
|
|
31
31
|
address airlock_
|
|
32
32
|
) BaseCoin(protocolRewardRecipient_, protocolRewards_, poolManager_, airlock_) {}
|
|
33
33
|
|
|
34
|
+
/// @inheritdoc ICoin
|
|
35
|
+
function initialize(
|
|
36
|
+
address payoutRecipient_,
|
|
37
|
+
address[] memory owners_,
|
|
38
|
+
string memory tokenURI_,
|
|
39
|
+
string memory name_,
|
|
40
|
+
string memory symbol_,
|
|
41
|
+
address platformReferrer_,
|
|
42
|
+
address currency_,
|
|
43
|
+
PoolKey memory poolKey_,
|
|
44
|
+
uint160 sqrtPriceX96,
|
|
45
|
+
PoolConfiguration memory poolConfiguration_
|
|
46
|
+
) public override {
|
|
47
|
+
if (payoutRecipient_ == address(0)) {
|
|
48
|
+
revert AddressZero();
|
|
49
|
+
}
|
|
50
|
+
super.initialize(payoutRecipient_, owners_, tokenURI_, name_, symbol_, platformReferrer_, currency_, poolKey_, sqrtPriceX96, poolConfiguration_);
|
|
51
|
+
}
|
|
52
|
+
|
|
34
53
|
/// @dev The initial mint and distribution of the coin supply.
|
|
35
54
|
/// Implements content coin specific distribution: 990M to liquidity pool, 10M to creator.
|
|
36
55
|
function _handleInitialDistribution() internal virtual override {
|
package/src/CreatorCoin.sol
CHANGED
|
@@ -46,6 +46,9 @@ contract CreatorCoin is ICreatorCoin, BaseCoin {
|
|
|
46
46
|
uint160 sqrtPriceX96,
|
|
47
47
|
PoolConfiguration memory poolConfiguration_
|
|
48
48
|
) public override(BaseCoin, ICoin) {
|
|
49
|
+
if (payoutRecipient_ == address(0)) {
|
|
50
|
+
revert AddressZero();
|
|
51
|
+
}
|
|
49
52
|
require(currency_ == CoinConstants.CREATOR_COIN_CURRENCY, InvalidCurrency());
|
|
50
53
|
|
|
51
54
|
super.initialize({
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
|
|
2
|
+
// This software is licensed under the Zora Delayed Open Source License.
|
|
3
|
+
// Under this license, you may use, copy, modify, and distribute this software for
|
|
4
|
+
// non-commercial purposes only. Commercial use and competitive products are prohibited
|
|
5
|
+
// until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
|
|
6
|
+
// at which point this software automatically becomes available under the MIT License.
|
|
7
|
+
// Full license terms available at: https://docs.zora.co/coins/license
|
|
8
|
+
pragma solidity ^0.8.28;
|
|
9
|
+
|
|
10
|
+
import {CoinConstants} from "./libs/CoinConstants.sol";
|
|
11
|
+
import {TickerUtils} from "./libs/TickerUtils.sol";
|
|
12
|
+
import {IHooks, PoolConfiguration, PoolKey, ICoin} from "./interfaces/ICoin.sol";
|
|
13
|
+
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
|
|
14
|
+
import {BaseCoin} from "./BaseCoin.sol";
|
|
15
|
+
import {IHasCoinType} from "./interfaces/ICoin.sol";
|
|
16
|
+
import {ITrendCoin} from "./interfaces/ITrendCoin.sol";
|
|
17
|
+
|
|
18
|
+
/// @title TrendCoin
|
|
19
|
+
/// @notice Trend coin implementation with no creator payout recipient
|
|
20
|
+
/// @dev TrendCoins have 100% of supply in the liquidity pool with no creator allocation.
|
|
21
|
+
/// Unlike ContentCoin and CreatorCoin, TrendCoins do not have a payoutRecipient or platformReferrer.
|
|
22
|
+
contract TrendCoin is BaseCoin, ITrendCoin {
|
|
23
|
+
/// @notice Base URI for trend coin metadata
|
|
24
|
+
string internal constant TREND_COIN_BASE_URI = "https://trends.theme.wtf/trend/";
|
|
25
|
+
|
|
26
|
+
address internal immutable metadataManager;
|
|
27
|
+
|
|
28
|
+
constructor(
|
|
29
|
+
address protocolRewardRecipient_,
|
|
30
|
+
address protocolRewards_,
|
|
31
|
+
IPoolManager poolManager_,
|
|
32
|
+
address airlock_,
|
|
33
|
+
address metadataManager_
|
|
34
|
+
) BaseCoin(protocolRewardRecipient_, protocolRewards_, poolManager_, airlock_) initializer {
|
|
35
|
+
// Zero address is valid when metadata is intended to be non-updatable
|
|
36
|
+
metadataManager = metadataManager_;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function totalSupplyForPositions() external pure override returns (uint256) {
|
|
40
|
+
return CoinConstants.TOTAL_SUPPLY;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function coinType() external pure override returns (IHasCoinType.CoinType) {
|
|
44
|
+
return IHasCoinType.CoinType.Trend;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function setContractURI(string memory newURI) external override {
|
|
48
|
+
require(msg.sender == metadataManager, OnlyMetadataManager());
|
|
49
|
+
_setContractURI(newURI);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function setNameAndSymbol(string memory newName, string memory newSymbol) external override {
|
|
53
|
+
require(msg.sender == metadataManager, OnlyMetadataManager());
|
|
54
|
+
_setNameAndSymbol(newName, newSymbol);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// @inheritdoc ITrendCoin
|
|
58
|
+
function initializeTrendCoin(
|
|
59
|
+
address[] memory owners_,
|
|
60
|
+
string memory symbol_,
|
|
61
|
+
PoolKey memory poolKey_,
|
|
62
|
+
uint160 sqrtPriceX96,
|
|
63
|
+
PoolConfiguration memory poolConfiguration_
|
|
64
|
+
) external {
|
|
65
|
+
// Validate ticker characters
|
|
66
|
+
require(TickerUtils.validateTickerCharacters(symbol_), InvalidTickerCharacters());
|
|
67
|
+
|
|
68
|
+
// Generate URI from base URI + encoded symbol
|
|
69
|
+
string memory uri = string.concat(TREND_COIN_BASE_URI, TickerUtils.tickerToUri(symbol_));
|
|
70
|
+
|
|
71
|
+
// Call parent initialize with derived values
|
|
72
|
+
// name = symbol for trend coins
|
|
73
|
+
// The initializer modifier is on BaseCoin.initialize, not here
|
|
74
|
+
BaseCoin.initialize({
|
|
75
|
+
payoutRecipient_: address(0),
|
|
76
|
+
owners_: owners_,
|
|
77
|
+
tokenURI_: uri,
|
|
78
|
+
name_: symbol_,
|
|
79
|
+
symbol_: symbol_,
|
|
80
|
+
platformReferrer_: address(0),
|
|
81
|
+
currency_: CoinConstants.CREATOR_COIN_CURRENCY,
|
|
82
|
+
poolKey_: poolKey_,
|
|
83
|
+
sqrtPriceX96: sqrtPriceX96,
|
|
84
|
+
poolConfiguration_: poolConfiguration_
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// @dev Legacy initialize function for ICoin compatibility
|
|
89
|
+
/// @notice Prefer using initializeTrendCoin for new deployments
|
|
90
|
+
function initialize(
|
|
91
|
+
address /* payoutRecipient_ */,
|
|
92
|
+
address[] memory /* owners_ */,
|
|
93
|
+
string memory /* tokenURI_ */,
|
|
94
|
+
string memory /* name_ */,
|
|
95
|
+
string memory /* symbol_ */,
|
|
96
|
+
address /* platformReferrer_ */,
|
|
97
|
+
address /* currency_ */,
|
|
98
|
+
PoolKey memory /* poolKey_ */,
|
|
99
|
+
uint160 /* sqrtPriceX96 */,
|
|
100
|
+
PoolConfiguration memory /* poolConfiguration_ */
|
|
101
|
+
) public override {
|
|
102
|
+
revert UseSpecificTrendCoinInitialize();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// @dev The initial mint and distribution of the coin supply.
|
|
106
|
+
/// TrendCoins have 100% of supply in the liquidity pool.
|
|
107
|
+
function _handleInitialDistribution() internal override {
|
|
108
|
+
_mint(address(this), CoinConstants.TOTAL_SUPPLY);
|
|
109
|
+
_transfer(address(this), address(poolKey.hooks), CoinConstants.TOTAL_SUPPLY);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// @notice TrendCoins have no platform referrer - always returns address(0)
|
|
113
|
+
/// @dev Overrides BaseCoin's platformReferrer which defaults to protocolRewardRecipient when not set
|
|
114
|
+
function platformReferrer() external pure override returns (address) {
|
|
115
|
+
return address(0);
|
|
116
|
+
}
|
|
117
|
+
}
|
package/src/ZoraFactoryImpl.sol
CHANGED
|
@@ -16,6 +16,8 @@ import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.s
|
|
|
16
16
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
17
17
|
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
18
18
|
import {CoinConfigurationVersions} from "./libs/CoinConfigurationVersions.sol";
|
|
19
|
+
import {CoinConstants} from "./libs/CoinConstants.sol";
|
|
20
|
+
import {TickerUtils} from "./libs/TickerUtils.sol";
|
|
19
21
|
import {ISwapRouter} from "./interfaces/ISwapRouter.sol";
|
|
20
22
|
import {IWETH} from "./interfaces/IWETH.sol";
|
|
21
23
|
import {IZoraFactory} from "./interfaces/IZoraFactory.sol";
|
|
@@ -34,6 +36,7 @@ import {IVersionedContract} from "@zoralabs/shared-contracts/interfaces/IVersion
|
|
|
34
36
|
import {CoinSetup} from "./libs/CoinSetup.sol";
|
|
35
37
|
import {CoinDopplerMultiCurve} from "./libs/CoinDopplerMultiCurve.sol";
|
|
36
38
|
import {ICreatorCoin} from "./interfaces/ICreatorCoin.sol";
|
|
39
|
+
import {ITrendCoin} from "./interfaces/ITrendCoin.sol";
|
|
37
40
|
import {DeployedCoinVersionLookup} from "./utils/DeployedCoinVersionLookup.sol";
|
|
38
41
|
import {IZoraHookRegistry} from "./interfaces/IZoraHookRegistry.sol";
|
|
39
42
|
|
|
@@ -52,16 +55,55 @@ contract ZoraFactoryImpl is
|
|
|
52
55
|
address public immutable coinV4Impl;
|
|
53
56
|
/// @notice The creator coin contract implementation address
|
|
54
57
|
address public immutable creatorCoinImpl;
|
|
58
|
+
/// @notice The trend coin contract implementation address
|
|
59
|
+
address public immutable trendCoinImpl;
|
|
55
60
|
/// @notice The uniswap v4 coin hook address
|
|
56
61
|
address public immutable hook;
|
|
57
62
|
/// @notice The zora hook registry address
|
|
58
63
|
address public immutable zoraHookRegistry;
|
|
59
64
|
|
|
60
|
-
|
|
65
|
+
/// @custom:storage-location erc7201:zora.coins.trendcointickers.storage
|
|
66
|
+
struct TrendCoinTickerStorage {
|
|
67
|
+
mapping(bytes32 => bool) usedTickerHashes;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// keccak256(abi.encode(uint256(keccak256("zora.coins.trendcointickers.storage")) - 1)) & ~bytes32(uint256(0xff))
|
|
71
|
+
bytes32 private constant TREND_COIN_TICKER_STORAGE_LOCATION = 0x57bdedf0ddfee9320a51cef29a2847cd7d7c32252cadecb7958561cc2d69ff00;
|
|
72
|
+
|
|
73
|
+
/// @custom:storage-location erc7201:zora.coins.trendcoinconfig.storage
|
|
74
|
+
struct TrendCoinConfigStorage {
|
|
75
|
+
bytes poolConfig;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// keccak256(abi.encode(uint256(keccak256("zora.coins.trendcoinconfig.storage")) - 1)) & ~bytes32(uint256(0xff))
|
|
79
|
+
bytes32 private constant TREND_COIN_CONFIG_STORAGE_LOCATION = 0xd1aa47a8d1a3f9b64aa4095f5f6c436e9b3a1eb90a61ab15f3a94d28bf1c0200;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @dev Returns the storage slot struct for trend coin ticker tracking
|
|
83
|
+
* @return $ Storage struct containing the usedTickerHashes mapping
|
|
84
|
+
*/
|
|
85
|
+
function _getTrendCoinTickerStorage() private pure returns (TrendCoinTickerStorage storage $) {
|
|
86
|
+
assembly {
|
|
87
|
+
$.slot := TREND_COIN_TICKER_STORAGE_LOCATION
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @dev Returns the storage slot struct for trend coin pool configuration
|
|
93
|
+
* @return $ Storage struct containing the poolConfig bytes
|
|
94
|
+
*/
|
|
95
|
+
function _getTrendCoinConfigStorage() private pure returns (TrendCoinConfigStorage storage $) {
|
|
96
|
+
assembly {
|
|
97
|
+
$.slot := TREND_COIN_CONFIG_STORAGE_LOCATION
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
constructor(address coinV4Impl_, address creatorCoinImpl_, address trendCoinImpl_, address hook_, address zoraHookRegistry_) {
|
|
61
102
|
_disableInitializers();
|
|
62
103
|
|
|
63
104
|
coinV4Impl = coinV4Impl_;
|
|
64
105
|
creatorCoinImpl = creatorCoinImpl_;
|
|
106
|
+
trendCoinImpl = trendCoinImpl_;
|
|
65
107
|
hook = hook_;
|
|
66
108
|
zoraHookRegistry = zoraHookRegistry_;
|
|
67
109
|
}
|
|
@@ -128,6 +170,73 @@ contract ZoraFactoryImpl is
|
|
|
128
170
|
return Clones.predictDeterministicAddress(getCoinImpl(CoinConfigurationVersions.getVersion(poolConfig)), salt, address(this));
|
|
129
171
|
}
|
|
130
172
|
|
|
173
|
+
/// @inheritdoc IZoraFactory
|
|
174
|
+
function deployTrendCoin(
|
|
175
|
+
string calldata symbol,
|
|
176
|
+
address postDeployHook,
|
|
177
|
+
bytes calldata postDeployHookData
|
|
178
|
+
) external payable nonReentrant returns (address coin, bytes memory postDeployHookDataOut) {
|
|
179
|
+
bytes32 tickerHashValue = TickerUtils.tickerHash(symbol);
|
|
180
|
+
|
|
181
|
+
// Check ticker uniqueness
|
|
182
|
+
TrendCoinTickerStorage storage $ = _getTrendCoinTickerStorage();
|
|
183
|
+
if ($.usedTickerHashes[tickerHashValue]) {
|
|
184
|
+
revert TickerAlreadyUsed(symbol);
|
|
185
|
+
}
|
|
186
|
+
$.usedTickerHashes[tickerHashValue] = true;
|
|
187
|
+
|
|
188
|
+
// Use ticker hash as salt for deterministic address
|
|
189
|
+
bytes32 salt = tickerHashValue;
|
|
190
|
+
|
|
191
|
+
coin = _createAndInitializeTrendCoin(symbol, salt);
|
|
192
|
+
postDeployHookDataOut = _executePostDeployHook(coin, postDeployHook, postDeployHookData);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/// @inheritdoc IZoraFactory
|
|
196
|
+
function trendCoinAddress(string calldata symbol) external view returns (address) {
|
|
197
|
+
bytes32 tickerHashValue = TickerUtils.tickerHash(symbol);
|
|
198
|
+
return Clones.predictDeterministicAddress(trendCoinImpl, tickerHashValue, address(this));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/// @dev Internal function to create and initialize a trend coin
|
|
202
|
+
/// @param symbol The ticker symbol (validation happens in TrendCoin.initializeTrendCoin)
|
|
203
|
+
/// @param coinSalt The salt for deterministic address generation
|
|
204
|
+
function _createAndInitializeTrendCoin(string memory symbol, bytes32 coinSalt) internal returns (address) {
|
|
205
|
+
// Clone the TrendCoin implementation
|
|
206
|
+
address coin = Clones.cloneDeterministic(trendCoinImpl, coinSalt);
|
|
207
|
+
|
|
208
|
+
// Get pool configuration from storage
|
|
209
|
+
bytes memory poolConfig = _getTrendCoinConfigStorage().poolConfig;
|
|
210
|
+
if (poolConfig.length == 0) {
|
|
211
|
+
revert TrendCoinPoolConfigNotSet();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
uint8 version = CoinConfigurationVersions.getVersion(poolConfig);
|
|
215
|
+
_setVersionForDeployedCoin(coin, version);
|
|
216
|
+
|
|
217
|
+
require(version == CoinConfigurationVersions.DOPPLER_MULTICURVE_UNI_V4_POOL_VERSION, InvalidConfig());
|
|
218
|
+
|
|
219
|
+
// Setup owners - factory owner is the owner of trend coins
|
|
220
|
+
address[] memory owners = new address[](1);
|
|
221
|
+
owners[0] = owner();
|
|
222
|
+
|
|
223
|
+
// Generate pool key and configuration
|
|
224
|
+
uint160 sqrtPriceX96;
|
|
225
|
+
bool isCoinToken0;
|
|
226
|
+
PoolConfiguration memory poolConfiguration;
|
|
227
|
+
address currency;
|
|
228
|
+
(, currency, sqrtPriceX96, isCoinToken0, poolConfiguration) = CoinSetup.generatePoolConfig(coin, poolConfig);
|
|
229
|
+
PoolKey memory poolKey = CoinSetup.buildPoolKey(coin, currency, isCoinToken0, IHooks(hook));
|
|
230
|
+
|
|
231
|
+
// Initialize using TrendCoin's simplified initialize
|
|
232
|
+
// Validation and URI generation happen inside TrendCoin
|
|
233
|
+
ITrendCoin(coin).initializeTrendCoin(owners, symbol, poolKey, sqrtPriceX96, poolConfiguration);
|
|
234
|
+
|
|
235
|
+
emit TrendCoinCreated(msg.sender, symbol, coin, poolKey, CoinCommon.hashPoolKey(poolKey), poolConfig, IVersionedContract(coin).contractVersion());
|
|
236
|
+
|
|
237
|
+
return coin;
|
|
238
|
+
}
|
|
239
|
+
|
|
131
240
|
function _executePostDeployHook(address coin, address deployHook, bytes calldata hookData) internal returns (bytes memory hookDataOut) {
|
|
132
241
|
if (deployHook != address(0)) {
|
|
133
242
|
if (!IERC165(deployHook).supportsInterface(type(IHasAfterCoinDeploy).interfaceId)) {
|
|
@@ -442,4 +551,36 @@ contract ZoraFactoryImpl is
|
|
|
442
551
|
function contentCoinHook() external view returns (address) {
|
|
443
552
|
return hook;
|
|
444
553
|
}
|
|
554
|
+
|
|
555
|
+
/// @inheritdoc IZoraFactory
|
|
556
|
+
function setTrendCoinPoolConfig(
|
|
557
|
+
address currency,
|
|
558
|
+
int24[] memory tickLower,
|
|
559
|
+
int24[] memory tickUpper,
|
|
560
|
+
uint16[] memory numDiscoveryPositions,
|
|
561
|
+
uint256[] memory maxDiscoverySupplyShare
|
|
562
|
+
) external onlyOwner {
|
|
563
|
+
// Validate arrays have matching lengths
|
|
564
|
+
require(
|
|
565
|
+
tickLower.length == tickUpper.length && tickLower.length == numDiscoveryPositions.length && tickLower.length == maxDiscoverySupplyShare.length,
|
|
566
|
+
InvalidConfig()
|
|
567
|
+
);
|
|
568
|
+
require(tickLower.length > 0, InvalidConfig());
|
|
569
|
+
require(currency == CoinConstants.CREATOR_COIN_CURRENCY, InvalidConfig());
|
|
570
|
+
|
|
571
|
+
bytes memory poolConfig = CoinConfigurationVersions.encodeDopplerMultiCurveUniV4(
|
|
572
|
+
currency,
|
|
573
|
+
tickLower,
|
|
574
|
+
tickUpper,
|
|
575
|
+
numDiscoveryPositions,
|
|
576
|
+
maxDiscoverySupplyShare
|
|
577
|
+
);
|
|
578
|
+
_getTrendCoinConfigStorage().poolConfig = poolConfig;
|
|
579
|
+
emit TrendCoinPoolConfigUpdated(poolConfig);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/// @inheritdoc IZoraFactory
|
|
583
|
+
function trendCoinPoolConfig() external view returns (bytes memory) {
|
|
584
|
+
return _getTrendCoinConfigStorage().poolConfig;
|
|
585
|
+
}
|
|
445
586
|
}
|
|
@@ -24,6 +24,8 @@ import {IZoraV4CoinHook} from "../interfaces/IZoraV4CoinHook.sol";
|
|
|
24
24
|
import {IMsgSender} from "../interfaces/IMsgSender.sol";
|
|
25
25
|
import {ITrustedMsgSenderProviderLookup} from "../interfaces/ITrustedMsgSenderProviderLookup.sol";
|
|
26
26
|
import {ICoin, IHasSwapPath, IHasRewardsRecipients, IHasCoinType} from "../interfaces/ICoin.sol";
|
|
27
|
+
import {IHasCreationInfo} from "../interfaces/IHasCreationInfo.sol";
|
|
28
|
+
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
27
29
|
import {IDeployedCoinVersionLookup} from "../interfaces/IDeployedCoinVersionLookup.sol";
|
|
28
30
|
import {IUpgradeableV4Hook, IUpgradeableDestinationV4Hook, IUpgradeableDestinationV4HookWithUpdateableFee, BurnedPosition} from "../interfaces/IUpgradeableV4Hook.sol";
|
|
29
31
|
import {IHooksUpgradeGate} from "../interfaces/IHooksUpgradeGate.sol";
|
|
@@ -41,6 +43,7 @@ import {LiquidityAmounts} from "../utils/uniswap/LiquidityAmounts.sol";
|
|
|
41
43
|
import {TickMath} from "../utils/uniswap/TickMath.sol";
|
|
42
44
|
import {ContractVersionBase, IVersionedContract} from "../version/ContractVersionBase.sol";
|
|
43
45
|
import {ISupportsLimitOrderFill} from "../interfaces/ISupportsLimitOrderFill.sol";
|
|
46
|
+
import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";
|
|
44
47
|
|
|
45
48
|
/// @title ZoraV4CoinHook
|
|
46
49
|
/// @notice Uniswap V4 hook that automatically handles fee collection and reward distributions on every swap,
|
|
@@ -295,8 +298,9 @@ contract ZoraV4CoinHook is
|
|
|
295
298
|
V4Liquidity.lockAndMint(poolManager, key, positions);
|
|
296
299
|
}
|
|
297
300
|
|
|
298
|
-
/// @notice Transiently stores the tick before a swap.
|
|
301
|
+
/// @notice Transiently stores the tick before a swap and calculates the launch fee.
|
|
299
302
|
/// @dev This is used in `_afterSwap` to determine the ticks crossed during the swap.
|
|
303
|
+
/// Also returns a dynamic fee that decays from 99% to 1% over 10 seconds after coin creation.
|
|
300
304
|
function _beforeSwap(
|
|
301
305
|
address sender,
|
|
302
306
|
PoolKey calldata key,
|
|
@@ -313,7 +317,61 @@ contract ZoraV4CoinHook is
|
|
|
313
317
|
TransientSlot.Int256Slot slot = TransientSlot.asInt256(CoinConstants._BEFORE_SWAP_TICK_SLOT);
|
|
314
318
|
TransientSlot.tstore(slot, int256(currentTick));
|
|
315
319
|
|
|
316
|
-
|
|
320
|
+
// Calculate launch fee
|
|
321
|
+
bytes32 poolKeyHash = CoinCommon.hashPoolKey(key);
|
|
322
|
+
address coin = poolCoins[poolKeyHash].coin;
|
|
323
|
+
uint24 fee = _calculateLaunchFee(coin);
|
|
324
|
+
|
|
325
|
+
return (BaseHook.beforeSwap.selector, BeforeSwapDelta.wrap(0), fee);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/// @notice Calculates the launch fee based on coin creation time
|
|
329
|
+
/// @dev Returns fee with OVERRIDE_FEE_FLAG to signal V4 to use this fee.
|
|
330
|
+
/// Fee decays linearly from LAUNCH_FEE_START (99%) to LP_FEE_V4 (1%) over LAUNCH_FEE_DURATION.
|
|
331
|
+
/// Returns LP_FEE_V4 for legacy coins that don't support IHasCreationInfo.
|
|
332
|
+
/// Bypasses launch fee (returns LP_FEE_V4) during initial coin deployment.
|
|
333
|
+
/// @param coin The coin address
|
|
334
|
+
/// @return fee The calculated fee with OVERRIDE_FEE_FLAG
|
|
335
|
+
function _calculateLaunchFee(address coin) internal view returns (uint24 fee) {
|
|
336
|
+
// Check if coin supports creation info interface (legacy coins won't)
|
|
337
|
+
try IERC165(coin).supportsInterface(type(IHasCreationInfo).interfaceId) returns (bool supported) {
|
|
338
|
+
if (!supported) {
|
|
339
|
+
// Legacy coin - use normal LP fee
|
|
340
|
+
return CoinConstants.OVERRIDE_FEE_FLAG | CoinConstants.LP_FEE_V4;
|
|
341
|
+
}
|
|
342
|
+
} catch {
|
|
343
|
+
// supportsInterface call failed - use normal LP fee
|
|
344
|
+
return CoinConstants.OVERRIDE_FEE_FLAG | CoinConstants.LP_FEE_V4;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Get creation info from coin
|
|
348
|
+
(uint256 creationTimestamp, bool isDeploying) = IHasCreationInfo(coin).creationInfo();
|
|
349
|
+
|
|
350
|
+
// Bypass launch fee during initial deployment
|
|
351
|
+
if (isDeploying) {
|
|
352
|
+
return CoinConstants.OVERRIDE_FEE_FLAG | CoinConstants.LP_FEE_V4;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Calculate elapsed time since creation
|
|
356
|
+
uint256 elapsed = block.timestamp - creationTimestamp;
|
|
357
|
+
|
|
358
|
+
// If launch fee duration has passed, use normal LP fee (0% for trend coins)
|
|
359
|
+
if (elapsed >= CoinConstants.LAUNCH_FEE_DURATION) {
|
|
360
|
+
try IHasCoinType(coin).coinType() returns (IHasCoinType.CoinType ct) {
|
|
361
|
+
if (ct == IHasCoinType.CoinType.Trend) {
|
|
362
|
+
return CoinConstants.OVERRIDE_FEE_FLAG;
|
|
363
|
+
}
|
|
364
|
+
} catch {}
|
|
365
|
+
return CoinConstants.OVERRIDE_FEE_FLAG | CoinConstants.LP_FEE_V4;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Linear decay: fee = startFee - (elapsed / duration) * (startFee - endFee)
|
|
369
|
+
uint256 startFee = CoinConstants.LAUNCH_FEE_START;
|
|
370
|
+
uint256 endFee = CoinConstants.LP_FEE_V4;
|
|
371
|
+
uint256 feeReduction = (elapsed * (startFee - endFee)) / CoinConstants.LAUNCH_FEE_DURATION;
|
|
372
|
+
fee = uint24(startFee - feeReduction);
|
|
373
|
+
|
|
374
|
+
return CoinConstants.OVERRIDE_FEE_FLAG | fee;
|
|
317
375
|
}
|
|
318
376
|
|
|
319
377
|
/// @notice Internal fn called when a swap is executed.
|
|
@@ -354,16 +412,18 @@ contract ZoraV4CoinHook is
|
|
|
354
412
|
|
|
355
413
|
(uint128 marketRewardsAmount0, uint128 marketRewardsAmount1) = CoinRewardsV4.mintLpReward(poolManager, key, fees0, fees1);
|
|
356
414
|
|
|
357
|
-
// convert remaining fees to payout currency for market rewards
|
|
358
|
-
|
|
415
|
+
// convert remaining fees to payout currency for market rewards, and distribute any partial swap remainders
|
|
416
|
+
address tradeReferrer = CoinRewardsV4.getTradeReferral(hookData);
|
|
417
|
+
CoinRewardsV4.swapFeesToPayoutAndDistribute(
|
|
359
418
|
poolManager,
|
|
360
419
|
marketRewardsAmount0,
|
|
361
420
|
marketRewardsAmount1,
|
|
362
|
-
payoutSwapPath
|
|
421
|
+
payoutSwapPath,
|
|
422
|
+
ICoin(coin),
|
|
423
|
+
tradeReferrer,
|
|
424
|
+
ICoin(coin).coinType()
|
|
363
425
|
);
|
|
364
426
|
|
|
365
|
-
_distributeMarketRewards(payoutCurrency, payoutAmount, ICoin(coin), CoinRewardsV4.getTradeReferral(hookData));
|
|
366
|
-
|
|
367
427
|
{
|
|
368
428
|
(address swapper, bool isTrustedSwapSenderAddress) = _getOriginalMsgSender(sender);
|
|
369
429
|
bool isCoinBuy = params.zeroForOne ? Currency.unwrap(key.currency1) == address(coin) : Currency.unwrap(key.currency0) == address(coin);
|
|
@@ -383,7 +443,12 @@ contract ZoraV4CoinHook is
|
|
|
383
443
|
}
|
|
384
444
|
|
|
385
445
|
(int24 tickBeforeSwap, int24 tickAfterSwap) = _getSwapTickRange(key);
|
|
386
|
-
|
|
446
|
+
|
|
447
|
+
// Derive fill direction from actual tick movement
|
|
448
|
+
if (tickAfterSwap != tickBeforeSwap) {
|
|
449
|
+
bool isCurrency0 = tickAfterSwap > tickBeforeSwap;
|
|
450
|
+
zoraLimitOrderBook.fill(key, isCurrency0, tickBeforeSwap, tickAfterSwap, CoinConstants.SENTINEL_DEFAULT_LIMIT_ORDER_FILL_COUNT, address(0));
|
|
451
|
+
}
|
|
387
452
|
|
|
388
453
|
return (BaseHook.afterSwap.selector, 0);
|
|
389
454
|
}
|
package/src/interfaces/ICoin.sol
CHANGED
|
@@ -60,7 +60,8 @@ interface IHasCoinType {
|
|
|
60
60
|
/// @notice The type of coin
|
|
61
61
|
enum CoinType {
|
|
62
62
|
Creator,
|
|
63
|
-
Content
|
|
63
|
+
Content,
|
|
64
|
+
Trend
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
/// @notice Returns the type of coin
|
|
@@ -78,6 +79,9 @@ interface ICoin is IERC165, IERC7572, IDopplerErrors, IHasRewardsRecipients, IHa
|
|
|
78
79
|
/// @notice Thrown when an invalid market type is specified
|
|
79
80
|
error InvalidMarketType();
|
|
80
81
|
|
|
82
|
+
/// @notice Thrown when an invalid currency is used for coin operations
|
|
83
|
+
error InvalidCurrency();
|
|
84
|
+
|
|
81
85
|
/// @notice Thrown when there are insufficient funds for an operation
|
|
82
86
|
error InsufficientFunds();
|
|
83
87
|
|
|
@@ -12,9 +12,6 @@ interface ICreatorCoin is ICoin {
|
|
|
12
12
|
/// @param vestingEndTime The timestamp when vesting ends
|
|
13
13
|
event CreatorVestingClaimed(address indexed recipient, uint256 claimAmount, uint256 totalClaimed, uint256 vestingStartTime, uint256 vestingEndTime);
|
|
14
14
|
|
|
15
|
-
/// @notice Thrown when an invalid currency is used for creator coin operations
|
|
16
|
-
error InvalidCurrency();
|
|
17
|
-
|
|
18
15
|
/// @notice Allows the creator payout recipient to claim vested tokens
|
|
19
16
|
/// @return claimAmount The amount of tokens claimed
|
|
20
17
|
function claimVesting() external returns (uint256);
|