@zoralabs/coins 2.5.0 → 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/.turbo/turbo-build$colon$js.log +143 -131
- package/CHANGELOG.md +20 -17
- package/abis/BaseCoin.json +5 -0
- package/abis/ContentCoin.json +5 -0
- package/abis/ICoin.json +5 -0
- package/abis/ICoinV3.json +5 -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 +953 -138
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +951 -138
- package/dist/index.js.map +1 -1
- package/dist/wagmiGenerated.d.ts +1380 -149
- package/dist/wagmiGenerated.d.ts.map +1 -1
- package/package/wagmiGenerated.ts +960 -139
- package/package.json +2 -2
- package/src/BaseCoin.sol +12 -12
- 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 +14 -6
- package/src/interfaces/ICoin.sol +5 -1
- package/src/interfaces/ICreatorCoin.sol +0 -3
- 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 +9 -1
- package/src/libs/CoinRewardsV4.sol +67 -19
- package/src/libs/TickerUtils.sol +84 -0
- package/src/libs/UniV4SwapToCurrency.sol +2 -1
- package/src/version/ContractVersionBase.sol +1 -1
- package/test/CreatorCoin.t.sol +2 -1
- package/test/Factory.t.sol +31 -5
- package/test/LaunchFee.t.sol +0 -2
- 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"
|
package/src/BaseCoin.sol
CHANGED
|
@@ -76,8 +76,8 @@ abstract contract BaseCoin is ICoin, IHasCreationInfo, ContractVersionBase, ERC2
|
|
|
76
76
|
string public tokenURI;
|
|
77
77
|
/// @notice The address of the coin creator
|
|
78
78
|
address public payoutRecipient;
|
|
79
|
-
/// @notice The address of the platform referrer
|
|
80
|
-
address
|
|
79
|
+
/// @notice The address of the platform referrer (internal storage, use platformReferrer() getter)
|
|
80
|
+
address internal _platformReferrer;
|
|
81
81
|
/// @notice The address of the currency
|
|
82
82
|
address public currency;
|
|
83
83
|
|
|
@@ -164,11 +164,6 @@ abstract contract BaseCoin is ICoin, IHasCreationInfo, ContractVersionBase, ERC2
|
|
|
164
164
|
string memory symbol_,
|
|
165
165
|
address platformReferrer_
|
|
166
166
|
) internal {
|
|
167
|
-
// Validate the creation parameters
|
|
168
|
-
if (payoutRecipient_ == address(0)) {
|
|
169
|
-
revert AddressZero();
|
|
170
|
-
}
|
|
171
|
-
|
|
172
167
|
_setNameAndSymbol(name_, symbol_);
|
|
173
168
|
|
|
174
169
|
// Set base contract state, leave name and symbol empty to save space.
|
|
@@ -179,12 +174,12 @@ abstract contract BaseCoin is ICoin, IHasCreationInfo, ContractVersionBase, ERC2
|
|
|
179
174
|
|
|
180
175
|
__MultiOwnable_init(owners_);
|
|
181
176
|
|
|
182
|
-
// Set mutable state
|
|
183
|
-
|
|
177
|
+
// Set mutable state (no validation here - subclasses validate if needed)
|
|
178
|
+
payoutRecipient = payoutRecipient_;
|
|
184
179
|
_setContractURI(tokenURI_);
|
|
185
180
|
|
|
186
181
|
// Store the referrer or use the protocol reward recipient if not set
|
|
187
|
-
|
|
182
|
+
_platformReferrer = platformReferrer_ == address(0) ? protocolRewardRecipient : platformReferrer_;
|
|
188
183
|
|
|
189
184
|
// Distribute the initial supply
|
|
190
185
|
_handleInitialDistribution();
|
|
@@ -216,7 +211,7 @@ abstract contract BaseCoin is ICoin, IHasCreationInfo, ContractVersionBase, ERC2
|
|
|
216
211
|
|
|
217
212
|
/// @notice Set the contract URI
|
|
218
213
|
/// @param newURI The new URI
|
|
219
|
-
function setContractURI(string memory newURI) external onlyOwner {
|
|
214
|
+
function setContractURI(string memory newURI) external virtual onlyOwner {
|
|
220
215
|
_setContractURI(newURI);
|
|
221
216
|
}
|
|
222
217
|
|
|
@@ -232,7 +227,7 @@ abstract contract BaseCoin is ICoin, IHasCreationInfo, ContractVersionBase, ERC2
|
|
|
232
227
|
return _name;
|
|
233
228
|
}
|
|
234
229
|
|
|
235
|
-
function setNameAndSymbol(string memory newName, string memory newSymbol) external onlyOwner {
|
|
230
|
+
function setNameAndSymbol(string memory newName, string memory newSymbol) external virtual onlyOwner {
|
|
236
231
|
_setNameAndSymbol(newName, newSymbol);
|
|
237
232
|
}
|
|
238
233
|
|
|
@@ -334,6 +329,11 @@ abstract contract BaseCoin is ICoin, IHasCreationInfo, ContractVersionBase, ERC2
|
|
|
334
329
|
return poolKey.hooks;
|
|
335
330
|
}
|
|
336
331
|
|
|
332
|
+
/// @inheritdoc IHasRewardsRecipients
|
|
333
|
+
function platformReferrer() external view virtual returns (address) {
|
|
334
|
+
return _platformReferrer;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
337
|
/// @notice Migrate liquidity from current hook to a new hook implementation
|
|
338
338
|
/// @param newHook Address of the new hook implementation
|
|
339
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
|
}
|
|
@@ -43,6 +43,7 @@ import {LiquidityAmounts} from "../utils/uniswap/LiquidityAmounts.sol";
|
|
|
43
43
|
import {TickMath} from "../utils/uniswap/TickMath.sol";
|
|
44
44
|
import {ContractVersionBase, IVersionedContract} from "../version/ContractVersionBase.sol";
|
|
45
45
|
import {ISupportsLimitOrderFill} from "../interfaces/ISupportsLimitOrderFill.sol";
|
|
46
|
+
import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";
|
|
46
47
|
|
|
47
48
|
/// @title ZoraV4CoinHook
|
|
48
49
|
/// @notice Uniswap V4 hook that automatically handles fee collection and reward distributions on every swap,
|
|
@@ -354,8 +355,13 @@ contract ZoraV4CoinHook is
|
|
|
354
355
|
// Calculate elapsed time since creation
|
|
355
356
|
uint256 elapsed = block.timestamp - creationTimestamp;
|
|
356
357
|
|
|
357
|
-
// If launch fee duration has passed, use normal LP fee
|
|
358
|
+
// If launch fee duration has passed, use normal LP fee (0% for trend coins)
|
|
358
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 {}
|
|
359
365
|
return CoinConstants.OVERRIDE_FEE_FLAG | CoinConstants.LP_FEE_V4;
|
|
360
366
|
}
|
|
361
367
|
|
|
@@ -406,16 +412,18 @@ contract ZoraV4CoinHook is
|
|
|
406
412
|
|
|
407
413
|
(uint128 marketRewardsAmount0, uint128 marketRewardsAmount1) = CoinRewardsV4.mintLpReward(poolManager, key, fees0, fees1);
|
|
408
414
|
|
|
409
|
-
// convert remaining fees to payout currency for market rewards
|
|
410
|
-
|
|
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(
|
|
411
418
|
poolManager,
|
|
412
419
|
marketRewardsAmount0,
|
|
413
420
|
marketRewardsAmount1,
|
|
414
|
-
payoutSwapPath
|
|
421
|
+
payoutSwapPath,
|
|
422
|
+
ICoin(coin),
|
|
423
|
+
tradeReferrer,
|
|
424
|
+
ICoin(coin).coinType()
|
|
415
425
|
);
|
|
416
426
|
|
|
417
|
-
_distributeMarketRewards(payoutCurrency, payoutAmount, ICoin(coin), CoinRewardsV4.getTradeReferral(hookData));
|
|
418
|
-
|
|
419
427
|
{
|
|
420
428
|
(address swapper, bool isTrustedSwapSenderAddress) = _getOriginalMsgSender(sender);
|
|
421
429
|
bool isCoinBuy = params.zeroForOne ? Currency.unwrap(key.currency1) == address(coin) : Currency.unwrap(key.currency0) == address(coin);
|
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);
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
// these needed to be imported so that their abis can be included in the generated package output.
|
|
11
|
+
|
|
12
|
+
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
|
|
13
|
+
import {IUniversalRouter} from "@uniswap/universal-router/contracts/interfaces/IUniversalRouter.sol";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.23;
|
|
3
|
+
|
|
4
|
+
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
|
|
5
|
+
import {PoolConfiguration} from "../types/PoolConfiguration.sol";
|
|
6
|
+
import {ITrendCoinErrors} from "./ITrendCoinErrors.sol";
|
|
7
|
+
|
|
8
|
+
interface ITrendCoin is ITrendCoinErrors {
|
|
9
|
+
/// @notice Thrown when an operation is attempted by an entity other than the metadata manager
|
|
10
|
+
error OnlyMetadataManager();
|
|
11
|
+
|
|
12
|
+
/// @notice Initializes a trend coin with simplified parameters
|
|
13
|
+
/// @dev Ticker validation, URI generation, and name derivation happen internally
|
|
14
|
+
/// @param owners_ Array of owner addresses for the coin
|
|
15
|
+
/// @param symbol_ The ticker symbol (also used as name)
|
|
16
|
+
/// @param poolKey_ The Uniswap V4 pool key
|
|
17
|
+
/// @param sqrtPriceX96 The initial sqrt price for the pool
|
|
18
|
+
/// @param poolConfiguration_ The pool configuration settings
|
|
19
|
+
function initializeTrendCoin(
|
|
20
|
+
address[] memory owners_,
|
|
21
|
+
string memory symbol_,
|
|
22
|
+
PoolKey memory poolKey_,
|
|
23
|
+
uint160 sqrtPriceX96,
|
|
24
|
+
PoolConfiguration memory poolConfiguration_
|
|
25
|
+
) external;
|
|
26
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.23;
|
|
3
|
+
|
|
4
|
+
/// @title ITrendCoinErrors
|
|
5
|
+
/// @notice Shared error interface for TrendCoin-related errors
|
|
6
|
+
/// @dev Used by both TrendCoin and ZoraFactoryImpl for consistent error handling
|
|
7
|
+
interface ITrendCoinErrors {
|
|
8
|
+
/// @notice Thrown when ticker symbol contains invalid characters
|
|
9
|
+
/// @dev Allowed characters: space (0x20), dash (0x2D), 0-9, A-Z, a-z
|
|
10
|
+
error InvalidTickerCharacters();
|
|
11
|
+
|
|
12
|
+
/// @notice Thrown when attempting to deploy a trend coin with a ticker that already exists
|
|
13
|
+
/// @param symbol The ticker symbol that was already used
|
|
14
|
+
error TickerAlreadyUsed(string symbol);
|
|
15
|
+
|
|
16
|
+
/// @notice Thrown when attempting to use the legacy initialize function for a trend coin
|
|
17
|
+
error UseSpecificTrendCoinInitialize();
|
|
18
|
+
}
|
|
@@ -4,8 +4,9 @@ pragma solidity ^0.8.23;
|
|
|
4
4
|
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
|
|
5
5
|
import {PoolKeyStruct} from "./ICoin.sol";
|
|
6
6
|
import {IDeployedCoinVersionLookup} from "./IDeployedCoinVersionLookup.sol";
|
|
7
|
+
import {ITrendCoinErrors} from "./ITrendCoinErrors.sol";
|
|
7
8
|
|
|
8
|
-
interface IZoraFactory is IDeployedCoinVersionLookup {
|
|
9
|
+
interface IZoraFactory is IDeployedCoinVersionLookup, ITrendCoinErrors {
|
|
9
10
|
/// @notice Emitted when a coin is created
|
|
10
11
|
/// @param caller The msg.sender address
|
|
11
12
|
/// @param payoutRecipient The address of the creator payout recipient
|
|
@@ -80,6 +81,16 @@ interface IZoraFactory is IDeployedCoinVersionLookup {
|
|
|
80
81
|
string version
|
|
81
82
|
);
|
|
82
83
|
|
|
84
|
+
/// @notice Emitted when a trend coin is created
|
|
85
|
+
/// @param caller The msg.sender address
|
|
86
|
+
/// @param symbol The symbol/ticker of the coin
|
|
87
|
+
/// @param coin The address of the coin
|
|
88
|
+
/// @param poolKey The uniswap v4 pool key
|
|
89
|
+
/// @param poolKeyHash The hash of the pool key
|
|
90
|
+
/// @param poolConfig The encoded pool configuration (curve config)
|
|
91
|
+
/// @param version The coin contract version
|
|
92
|
+
event TrendCoinCreated(address indexed caller, string symbol, address coin, PoolKey poolKey, bytes32 poolKeyHash, bytes poolConfig, string version);
|
|
93
|
+
|
|
83
94
|
/// @notice Thrown when ETH is sent with a transaction but the currency is not WETH
|
|
84
95
|
error EthTransferInvalid();
|
|
85
96
|
|
|
@@ -97,6 +108,13 @@ interface IZoraFactory is IDeployedCoinVersionLookup {
|
|
|
97
108
|
/// @notice Thrwon when an invalid config version is provided
|
|
98
109
|
error InvalidConfig();
|
|
99
110
|
|
|
111
|
+
/// @notice Thrown when trying to deploy a trend coin before the pool config has been set
|
|
112
|
+
error TrendCoinPoolConfigNotSet();
|
|
113
|
+
|
|
114
|
+
/// @notice Emitted when the trend coin pool config is updated
|
|
115
|
+
/// @param poolConfig The new pool configuration
|
|
116
|
+
event TrendCoinPoolConfigUpdated(bytes poolConfig);
|
|
117
|
+
|
|
100
118
|
/// @dev Deprecated: use `deployCreatorCoin` instead that has a salt and post-deploy hook specified
|
|
101
119
|
function deployCreatorCoin(
|
|
102
120
|
address payoutRecipient,
|
|
@@ -219,4 +237,45 @@ interface IZoraFactory is IDeployedCoinVersionLookup {
|
|
|
219
237
|
|
|
220
238
|
/// @notice The address of the Zora hook registry
|
|
221
239
|
function zoraHookRegistry() external view returns (address);
|
|
240
|
+
|
|
241
|
+
/// @notice Creates a new trend coin with an optional hook that runs after the coin is deployed.
|
|
242
|
+
/// Enables buying initial supply by supporting ETH transfers to the post-deploy hook.
|
|
243
|
+
/// @dev TrendCoins have no payout recipient or platform referrer, and 100% of supply goes to the liquidity pool
|
|
244
|
+
/// @param symbol The ticker symbol for the trend coin (must be unique, case-insensitive)
|
|
245
|
+
/// @param postDeployHook The address of the hook to run after the coin is deployed
|
|
246
|
+
/// @param postDeployHookData The data to pass to the hook
|
|
247
|
+
/// @return coin The address of the deployed trend coin
|
|
248
|
+
/// @return postDeployHookDataOut The data returned by the hook
|
|
249
|
+
function deployTrendCoin(
|
|
250
|
+
string calldata symbol,
|
|
251
|
+
address postDeployHook,
|
|
252
|
+
bytes calldata postDeployHookData
|
|
253
|
+
) external payable returns (address coin, bytes memory postDeployHookDataOut);
|
|
254
|
+
|
|
255
|
+
/// @notice Predicts the address of a trend coin that will be deployed with the given ticker
|
|
256
|
+
/// @param symbol The ticker symbol for the trend coin
|
|
257
|
+
/// @return The address of the trend coin contract
|
|
258
|
+
function trendCoinAddress(string calldata symbol) external view returns (address);
|
|
259
|
+
|
|
260
|
+
/// @notice The trend coin contract implementation address
|
|
261
|
+
function trendCoinImpl() external view returns (address);
|
|
262
|
+
|
|
263
|
+
/// @notice Sets the pool configuration for trend coins
|
|
264
|
+
/// @param currency The currency address for the pool (e.g., ZORA token)
|
|
265
|
+
/// @param tickLower Array of lower tick bounds for each curve
|
|
266
|
+
/// @param tickUpper Array of upper tick bounds for each curve
|
|
267
|
+
/// @param numDiscoveryPositions Array of number of discovery positions for each curve
|
|
268
|
+
/// @param maxDiscoverySupplyShare Array of max supply share (in WAD) for each curve
|
|
269
|
+
/// @dev Can only be called by the contract owner. Arrays must all be the same length.
|
|
270
|
+
function setTrendCoinPoolConfig(
|
|
271
|
+
address currency,
|
|
272
|
+
int24[] memory tickLower,
|
|
273
|
+
int24[] memory tickUpper,
|
|
274
|
+
uint16[] memory numDiscoveryPositions,
|
|
275
|
+
uint256[] memory maxDiscoverySupplyShare
|
|
276
|
+
) external;
|
|
277
|
+
|
|
278
|
+
/// @notice Returns the current pool configuration for trend coins
|
|
279
|
+
/// @return The encoded pool configuration
|
|
280
|
+
function trendCoinPoolConfig() external view returns (bytes memory);
|
|
222
281
|
}
|