@zoralabs/coins 1.1.2 → 2.1.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.
Files changed (78) hide show
  1. package/.turbo/turbo-build.log +107 -110
  2. package/CHANGELOG.md +50 -0
  3. package/README.md +48 -1
  4. package/abis/BaseCoin.json +442 -0
  5. package/abis/BaseZoraV4CoinHook.json +6 -2
  6. package/abis/CoinTest.json +3 -246
  7. package/abis/CoinUniV4Test.json +20 -0
  8. package/abis/ContentCoinHook.json +6 -2
  9. package/abis/CreatorCoinHook.json +6 -2
  10. package/abis/FactoryTest.json +8 -133
  11. package/abis/FeeEstimatorHook.json +6 -2
  12. package/abis/HooksTest.json +0 -26
  13. package/abis/ICoin.json +378 -0
  14. package/abis/ICoinV3.json +378 -0
  15. package/abis/IZoraFactory.json +0 -18
  16. package/abis/IZoraV4CoinHook.json +2 -2
  17. package/abis/LiquidityMigrationTest.json +101 -0
  18. package/abis/MockBadFactory.json +15 -0
  19. package/abis/Ownable2StepUpgradeable.json +138 -0
  20. package/abis/ZoraFactoryImpl.json +38 -65
  21. package/addresses/8453.json +5 -5
  22. package/dist/index.cjs +272 -268
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.js +270 -266
  25. package/dist/index.js.map +1 -1
  26. package/dist/wagmiGenerated.d.ts +397 -470
  27. package/dist/wagmiGenerated.d.ts.map +1 -1
  28. package/package/wagmiGenerated.ts +275 -271
  29. package/package.json +3 -3
  30. package/script/DeployPostDeploymentHooks.s.sol +2 -2
  31. package/script/TestBackingCoinSwap.s.sol +9 -9
  32. package/script/TestV4Swap.s.sol +9 -9
  33. package/script/UpgradeFactoryImpl.s.sol +0 -1
  34. package/src/BaseCoin.sol +109 -6
  35. package/src/ContentCoin.sol +45 -0
  36. package/src/CreatorCoin.sol +7 -5
  37. package/src/ZoraFactoryImpl.sol +12 -95
  38. package/src/deployment/CoinsDeployerBase.sol +13 -30
  39. package/src/hooks/BaseZoraV4CoinHook.sol +8 -6
  40. package/src/hooks/deployment/BuySupplyWithSwapRouterHook.sol +4 -5
  41. package/src/interfaces/ICoin.sol +67 -1
  42. package/src/interfaces/ICreatorCoin.sol +2 -2
  43. package/src/interfaces/IZoraFactory.sol +0 -5
  44. package/src/interfaces/IZoraV4CoinHook.sol +1 -1
  45. package/src/libs/CoinConfigurationVersions.sol +1 -39
  46. package/src/libs/CoinRewardsV4.sol +2 -2
  47. package/src/libs/CoinSetup.sol +1 -4
  48. package/src/libs/UniV4SwapHelper.sol +1 -1
  49. package/src/libs/UniV4SwapToCurrency.sol +2 -2
  50. package/src/libs/V4Liquidity.sol +1 -1
  51. package/src/version/ContractVersionBase.sol +1 -1
  52. package/test/Coin.t.sol +112 -535
  53. package/test/CoinUniV4.t.sol +66 -10
  54. package/test/DeploymentHooks.t.sol +5 -102
  55. package/test/Factory.t.sol +49 -291
  56. package/test/LiquidityMigration.t.sol +160 -2
  57. package/test/MultiOwnable.t.sol +36 -36
  58. package/test/Upgrades.t.sol +23 -42
  59. package/test/utils/BaseTest.sol +39 -84
  60. package/test/utils/FeeEstimatorHook.sol +3 -3
  61. package/wagmi.config.ts +2 -2
  62. package/abis/Coin.json +0 -1912
  63. package/abis/DopplerUniswapV3Test.json +0 -800
  64. package/abis/ICoinV4.json +0 -1048
  65. package/abis/Simulate.json +0 -29
  66. package/abis/UniV3BuySell.json +0 -12
  67. package/abis/UniV3Errors.json +0 -32
  68. package/script/Simulate.s.sol +0 -59
  69. package/src/Coin.sol +0 -236
  70. package/src/CoinV4.sol +0 -151
  71. package/src/interfaces/ICoinV4.sol +0 -74
  72. package/src/libs/CoinDopplerUniV3.sol +0 -50
  73. package/src/libs/CoinRewards.sol +0 -201
  74. package/src/libs/CoinSetupV3.sol +0 -50
  75. package/src/libs/UniV3BuySell.sol +0 -231
  76. package/src/libs/UniV3Errors.sol +0 -11
  77. package/test/CoinDopplerUniV3.t.sol +0 -310
  78. /package/abis/{CoinV4.json → ContentCoin.json} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zoralabs/coins",
3
- "version": "1.1.2",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -35,9 +35,9 @@
35
35
  "tsx": "^3.13.0",
36
36
  "typescript": "^5.2.2",
37
37
  "viem": "^2.21.18",
38
- "@zoralabs/shared-contracts": "^0.0.5",
38
+ "@zoralabs/tsconfig": "^0.0.1",
39
39
  "@zoralabs/shared-scripts": "^0.0.0",
40
- "@zoralabs/tsconfig": "^0.0.1"
40
+ "@zoralabs/shared-contracts": "^0.0.5"
41
41
  },
42
42
  "scripts": {
43
43
  "build": "pnpm run wagmi:generate && pnpm run copy-abis && pnpm run prettier:write && tsup",
@@ -10,9 +10,9 @@ contract DeployHooks is CoinsDeployerBase {
10
10
 
11
11
  vm.startBroadcast();
12
12
 
13
- address buySupplyWithSwapRouterHook = address(deployBuySupplyWithSwapRouterHook(deployment));
13
+ // address buySupplyWithSwapRouterHook = address(deployBuySupplyWithSwapRouterHook(deployment));
14
14
 
15
- deployment.buySupplyWithSwapRouterHook = buySupplyWithSwapRouterHook;
15
+ // deployment.buySupplyWithSwapRouterHook = buySupplyWithSwapRouterHook;
16
16
 
17
17
  vm.stopBroadcast();
18
18
 
@@ -8,7 +8,7 @@ import {IZoraFactory} from "../src/interfaces/IZoraFactory.sol";
8
8
  import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
9
9
  import {MarketConstants} from "../src/libs/MarketConstants.sol";
10
10
  import {UniV4SwapHelper} from "../src/libs/UniV4SwapHelper.sol";
11
- import {CoinV4} from "../src/CoinV4.sol";
11
+ import {ContentCoin} from "../src/ContentCoin.sol";
12
12
  import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
13
13
  import {IUniversalRouter} from "@uniswap/universal-router/contracts/interfaces/IUniversalRouter.sol";
14
14
  import {IPermit2} from "permit2/src/interfaces/IPermit2.sol";
@@ -16,7 +16,7 @@ import {MockERC20} from "../test/mocks/MockERC20.sol";
16
16
  import {MarketConstants} from "../src/libs/MarketConstants.sol";
17
17
  import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
18
18
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
19
- import {ICoinV4} from "../src/interfaces/ICoinV4.sol";
19
+ import {ICoin} from "../src/interfaces/ICoin.sol";
20
20
 
21
21
  import {console} from "forge-std/console.sol";
22
22
 
@@ -34,7 +34,7 @@ contract TestV4Swap is CoinsDeployerBase {
34
34
  string memory uri,
35
35
  address createReferral,
36
36
  bytes32 salt
37
- ) internal returns (ICoinV4 coin) {
37
+ ) internal returns (ICoin coin) {
38
38
  CoinsDeployment memory deployment = readDeployment();
39
39
  address[] memory owners = new address[](1);
40
40
  owners[0] = creator;
@@ -54,10 +54,10 @@ contract TestV4Swap is CoinsDeployerBase {
54
54
  salt
55
55
  );
56
56
 
57
- coin = ICoinV4(coinAddress);
57
+ coin = ICoin(coinAddress);
58
58
  }
59
59
 
60
- function _swap(address currencyIn, uint128 amountIn, ICoinV4 coin, address trader, address tradeReferral) internal returns (uint256 amountOut) {
60
+ function _swap(address currencyIn, uint128 amountIn, ICoin coin, address trader, address tradeReferral) internal returns (uint256 amountOut) {
61
61
  uint128 minAmountOut = 0;
62
62
 
63
63
  PoolKey memory poolKey = coin.getPoolKey();
@@ -106,8 +106,8 @@ contract TestV4Swap is CoinsDeployerBase {
106
106
  address createReferral = 0xC077e4cC02fa01A5b7fAca1acE9BBe9f5ac5Af9F;
107
107
  address tradeReferral = 0xC077e4cC02fa01A5b7fAca1acE9BBe9f5ac5Af9F;
108
108
 
109
- ICoinV4 backingCoin = _deployCoin(zora, trader, "Backing Coin", "BACK", "https://testc.com", createReferral, bytes32("creator"));
110
- ICoinV4 contentCoin = _deployCoin(
109
+ ICoin backingCoin = _deployCoin(zora, trader, "Backing Coin", "BACK", "https://testc.com", createReferral, bytes32("creator"));
110
+ ICoin contentCoin = _deployCoin(
111
111
  address(backingCoin),
112
112
  trader,
113
113
  "Content Coin",
@@ -116,8 +116,8 @@ contract TestV4Swap is CoinsDeployerBase {
116
116
  createReferral,
117
117
  bytes32("content coin")
118
118
  );
119
- // ICoinV4 backingCoin = ICoinV4(0xeA734b5997F35cD469921cCa7BB9A03C104f2f64);
120
- // ICoinV4 contentCoin = ICoinV4(0x72218BFEEc7D556BD3Dd8eFf2a317CEd49533769);
119
+ // ICoin backingCoin = ICoin(0xeA734b5997F35cD469921cCa7BB9A03C104f2f64);
120
+ // ICoin contentCoin = ICoin(0x72218BFEEc7D556BD3Dd8eFf2a317CEd49533769);
121
121
 
122
122
  console.log("backingCoin", address(backingCoin));
123
123
  console.log("contentCoin", address(contentCoin));
@@ -8,7 +8,7 @@ import {IZoraFactory} from "../src/interfaces/IZoraFactory.sol";
8
8
  import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
9
9
  import {MarketConstants} from "../src/libs/MarketConstants.sol";
10
10
  import {UniV4SwapHelper} from "../src/libs/UniV4SwapHelper.sol";
11
- import {CoinV4} from "../src/CoinV4.sol";
11
+ import {ContentCoin} from "../src/ContentCoin.sol";
12
12
  import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
13
13
  import {IUniversalRouter} from "@uniswap/universal-router/contracts/interfaces/IUniversalRouter.sol";
14
14
  import {IPermit2} from "permit2/src/interfaces/IPermit2.sol";
@@ -16,7 +16,7 @@ import {MockERC20} from "../test/mocks/MockERC20.sol";
16
16
  import {MarketConstants} from "../src/libs/MarketConstants.sol";
17
17
  import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
18
18
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
19
- import {ICoinV4} from "../src/interfaces/ICoinV4.sol";
19
+ import {ICoin} from "../src/interfaces/ICoin.sol";
20
20
 
21
21
  import {console} from "forge-std/console.sol";
22
22
 
@@ -31,7 +31,7 @@ contract TestV4Swap is CoinsDeployerBase {
31
31
  currency.mint(getUniswapV4PoolManager(), 1000000 ether);
32
32
  }
33
33
 
34
- function _deployMockCoin(address currency, address creator, address createReferral, bytes32 salt) internal returns (ICoinV4 coin) {
34
+ function _deployMockCoin(address currency, address creator, address createReferral, bytes32 salt) internal returns (ICoin coin) {
35
35
  CoinsDeployment memory deployment = readDeployment();
36
36
  address[] memory owners = new address[](1);
37
37
  owners[0] = creator;
@@ -51,10 +51,10 @@ contract TestV4Swap is CoinsDeployerBase {
51
51
  salt
52
52
  );
53
53
 
54
- coin = ICoinV4(coinAddress);
54
+ coin = ICoin(coinAddress);
55
55
  }
56
56
 
57
- function _swap(address currencyIn, uint128 amountIn, ICoinV4 coin, address trader, address tradeReferral) internal returns (uint256 amountOut) {
57
+ function _swap(address currencyIn, uint128 amountIn, ICoin coin, address trader, address tradeReferral) internal returns (uint256 amountOut) {
58
58
  uint128 minAmountOut = 0;
59
59
 
60
60
  PoolKey memory poolKey = coin.getPoolKey();
@@ -101,12 +101,12 @@ contract TestV4Swap is CoinsDeployerBase {
101
101
 
102
102
  // MockERC20 currency = _deployMockCurrency();
103
103
 
104
- // ICoinV4 backingCoin = _deployMockCoin(address(currency), trader, createReferral, bytes32("backing coin"));
105
- // ICoinV4 contentCoin = _deployMockCoin(address(backingCoin), trader, createReferral, bytes32("content coin"));
104
+ // ICoin backingCoin = _deployMockCoin(address(currency), trader, createReferral, bytes32("backing coin"));
105
+ // ICoin contentCoin = _deployMockCoin(address(backingCoin), trader, createReferral, bytes32("content coin"));
106
106
 
107
107
  MockERC20 currency = MockERC20(0x1b183Bd0E2c03Fc830F4d813bA37E82F9F97cA21);
108
- ICoinV4 backingCoin = ICoinV4(0x7D74416C4c295A592Fc6F9232911C945354b253C);
109
- ICoinV4 contentCoin = ICoinV4(0xf6d6660bcdA588F7f99e2961f279f500fB501730);
108
+ ICoin backingCoin = ICoin(0x7D74416C4c295A592Fc6F9232911C945354b253C);
109
+ ICoin contentCoin = ICoin(0xf6d6660bcdA588F7f99e2961f279f500fB501730);
110
110
 
111
111
  console.log("currency", address(currency));
112
112
  console.log("backingCoin", address(backingCoin));
@@ -11,7 +11,6 @@ contract UpgradeFactoryImpl is CoinsDeployerBase {
11
11
  vm.startBroadcast();
12
12
 
13
13
  ZoraFactoryImpl zoraFactoryImpl = deployZoraFactoryImpl(
14
- deployment.coinV3Impl,
15
14
  deployment.coinV4Impl,
16
15
  deployment.creatorCoinImpl,
17
16
  deployment.zoraV4CoinHook,
package/src/BaseCoin.sol CHANGED
@@ -19,6 +19,15 @@ import {IAirlock} from "./interfaces/IAirlock.sol";
19
19
  import {IProtocolRewards} from "./interfaces/IProtocolRewards.sol";
20
20
  import {IWETH} from "./interfaces/IWETH.sol";
21
21
 
22
+ import {IPoolManager, PoolKey, Currency, IHooks} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
23
+ import {IHasPoolKey, IHasSwapPath} from "./interfaces/ICoin.sol";
24
+ import {PoolConfiguration} from "./types/PoolConfiguration.sol";
25
+ import {UniV4SwapToCurrency} from "./libs/UniV4SwapToCurrency.sol";
26
+ import {PathKey} from "@uniswap/v4-periphery/src/libraries/PathKey.sol";
27
+ import {IDeployedCoinVersionLookup} from "./interfaces/IDeployedCoinVersionLookup.sol";
28
+ import {IUpgradeableV4Hook} from "./interfaces/IUpgradeableV4Hook.sol";
29
+ import {CoinCommon} from "./libs/CoinCommon.sol";
30
+
22
31
  import {Address} from "@openzeppelin/contracts/utils/Address.sol";
23
32
  import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
24
33
  import {ERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
@@ -33,8 +42,6 @@ import {CoinConstants} from "./libs/CoinConstants.sol";
33
42
  import {MarketConstants} from "./libs/MarketConstants.sol";
34
43
  import {LpPosition} from "./types/LpPosition.sol";
35
44
  import {PoolState} from "./types/PoolState.sol";
36
- import {CoinSetupV3, UniV3Config, CoinV3Config} from "./libs/CoinSetupV3.sol";
37
- import {UniV3BuySell, CoinConfig} from "./libs/UniV3BuySell.sol";
38
45
 
39
46
  /*
40
47
  $$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\
@@ -56,6 +63,15 @@ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable
56
63
  /// @notice The address of the Airlock contract, ownership is used for a protocol fee split.
57
64
  address public immutable airlock;
58
65
 
66
+ /// @notice The Uniswap v4 pool manager singleton contract reference.
67
+ IPoolManager public immutable poolManager;
68
+
69
+ /// @notice The pool key for the coin. Type from Uniswap V4 core.
70
+ PoolKey internal poolKey;
71
+
72
+ /// @notice The configuration for the pool.
73
+ PoolConfiguration internal poolConfiguration;
74
+
59
75
  /// @notice The metadata URI
60
76
  string public tokenURI;
61
77
  /// @notice The address of the coin creator
@@ -76,24 +92,53 @@ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable
76
92
  * @param _protocolRewards The address of the protocol rewards contract
77
93
  * @param _airlock The address of the Airlock contract
78
94
  */
79
- constructor(address _protocolRewardRecipient, address _protocolRewards, address _airlock) initializer {
95
+ constructor(address _protocolRewardRecipient, address _protocolRewards, IPoolManager poolManager_, address _airlock) initializer {
80
96
  if (_protocolRewardRecipient == address(0)) {
81
97
  revert AddressZero();
82
98
  }
83
99
  if (_protocolRewards == address(0)) {
84
100
  revert AddressZero();
85
101
  }
86
-
102
+ if (address(poolManager_) == address(0)) {
103
+ revert AddressZero();
104
+ }
87
105
  if (_airlock == address(0)) {
88
106
  revert AddressZero();
89
107
  }
90
108
 
91
109
  protocolRewardRecipient = _protocolRewardRecipient;
92
110
  protocolRewards = _protocolRewards;
111
+ poolManager = poolManager_;
93
112
  airlock = _airlock;
94
113
  }
95
114
 
96
- /// @notice Initializes a new coin
115
+ /// @inheritdoc ICoin
116
+ function initialize(
117
+ address payoutRecipient_,
118
+ address[] memory owners_,
119
+ string memory tokenURI_,
120
+ string memory name_,
121
+ string memory symbol_,
122
+ address platformReferrer_,
123
+ address currency_,
124
+ PoolKey memory poolKey_,
125
+ uint160 sqrtPriceX96,
126
+ PoolConfiguration memory poolConfiguration_
127
+ ) public virtual initializer {
128
+ currency = currency_;
129
+ // we need to set this before initialization, because
130
+ // distributing currency relies on the poolkey being set since the hooks
131
+ // are retrieved from there
132
+ poolKey = poolKey_;
133
+ poolConfiguration = poolConfiguration_;
134
+
135
+ _initialize(payoutRecipient_, owners_, tokenURI_, name_, symbol_, platformReferrer_);
136
+
137
+ // initialize the pool - the hook will mint its positions in the afterInitialize callback
138
+ poolManager.initialize(poolKey, sqrtPriceX96);
139
+ }
140
+
141
+ /// @notice Initializes a new coin (internal version)
97
142
  /// @param payoutRecipient_ The address of the coin creator
98
143
  /// @param tokenURI_ The metadata URI
99
144
  /// @param name_ The coin name
@@ -210,7 +255,9 @@ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable
210
255
  interfaceId == type(ICoin).interfaceId ||
211
256
  interfaceId == type(ICoinComments).interfaceId ||
212
257
  interfaceId == type(IERC7572).interfaceId ||
213
- interfaceId == type(IHasRewardsRecipients).interfaceId;
258
+ interfaceId == type(IHasRewardsRecipients).interfaceId ||
259
+ interfaceId == type(IHasPoolKey).interfaceId ||
260
+ type(IHasSwapPath).interfaceId == interfaceId;
214
261
  }
215
262
 
216
263
  /// @dev Overrides ERC20's _update function to emit a superset `CoinTransfer` event
@@ -245,4 +292,60 @@ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable
245
292
  function dopplerFeeRecipient() public view returns (address) {
246
293
  return IAirlock(airlock).owner();
247
294
  }
295
+
296
+ /// @inheritdoc IHasPoolKey
297
+ function getPoolKey() public view returns (PoolKey memory) {
298
+ return poolKey;
299
+ }
300
+
301
+ /// @inheritdoc ICoin
302
+ function getPoolConfiguration() public view returns (PoolConfiguration memory) {
303
+ return poolConfiguration;
304
+ }
305
+
306
+ /// @inheritdoc ICoin
307
+ function hooks() external view returns (IHooks) {
308
+ return poolKey.hooks;
309
+ }
310
+
311
+ /// @notice Migrate liquidity from current hook to a new hook implementation
312
+ /// @param newHook Address of the new hook implementation
313
+ /// @param additionalData Additional data to pass to the new hook during initialization
314
+ function migrateLiquidity(address newHook, bytes calldata additionalData) external onlyOwner returns (PoolKey memory newPoolKey) {
315
+ newPoolKey = IUpgradeableV4Hook(address(poolKey.hooks)).migrateLiquidity(newHook, poolKey, additionalData);
316
+
317
+ emit LiquidityMigrated(poolKey, CoinCommon.hashPoolKey(poolKey), newPoolKey, CoinCommon.hashPoolKey(newPoolKey));
318
+
319
+ poolKey = newPoolKey;
320
+ }
321
+
322
+ /// @inheritdoc IHasSwapPath
323
+ function getPayoutSwapPath(IDeployedCoinVersionLookup coinVersionLookup) external view returns (IHasSwapPath.PayoutSwapPath memory payoutSwapPath) {
324
+ // if to swap in is this currency,
325
+ // if backing currency is a coin, then recursively get the path from the coin
326
+ payoutSwapPath.currencyIn = Currency.wrap(address(this));
327
+
328
+ // swap to backing currency
329
+ PathKey memory thisPathKey = PathKey({
330
+ intermediateCurrency: Currency.wrap(currency),
331
+ fee: poolKey.fee,
332
+ tickSpacing: poolKey.tickSpacing,
333
+ hooks: poolKey.hooks,
334
+ hookData: ""
335
+ });
336
+
337
+ // get backing currency swap path - if the backing currency is a v4 coin and has a swap path.
338
+ PathKey[] memory subPath = UniV4SwapToCurrency.getSubSwapPath(currency, coinVersionLookup);
339
+
340
+ if (subPath.length > 0) {
341
+ payoutSwapPath.path = new PathKey[](1 + subPath.length);
342
+ payoutSwapPath.path[0] = thisPathKey;
343
+ for (uint256 i = 0; i < subPath.length; i++) {
344
+ payoutSwapPath.path[i + 1] = subPath[i];
345
+ }
346
+ } else {
347
+ payoutSwapPath.path = new PathKey[](1);
348
+ payoutSwapPath.path[0] = thisPathKey;
349
+ }
350
+ }
248
351
  }
@@ -0,0 +1,45 @@
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.23;
9
+
10
+ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
11
+ import {BaseCoin} from "./BaseCoin.sol";
12
+ import {CoinConstants} from "./libs/CoinConstants.sol";
13
+
14
+ /**
15
+ * @title ContentCoin
16
+ * @notice Content coin implementation that uses creator coins as backing currency
17
+ * @dev Inherits from BaseCoin and implements content-specific distribution logic
18
+ */
19
+ contract ContentCoin is BaseCoin {
20
+ /// @notice The constructor for the static ContentCoin contract deployment shared across all content coins.
21
+ /// @dev All arguments are required and cannot be set to the 0 address.
22
+ /// @param protocolRewardRecipient_ The address of the protocol reward recipient
23
+ /// @param protocolRewards_ The address of the protocol rewards contract
24
+ /// @param poolManager_ The address of the pool manager
25
+ /// @param airlock_ The address of the Airlock contract, ownership is used for a protocol fee split.
26
+ constructor(
27
+ address protocolRewardRecipient_,
28
+ address protocolRewards_,
29
+ IPoolManager poolManager_,
30
+ address airlock_
31
+ ) BaseCoin(protocolRewardRecipient_, protocolRewards_, poolManager_, airlock_) {}
32
+
33
+ /// @dev The initial mint and distribution of the coin supply.
34
+ /// Implements content coin specific distribution: 990M to liquidity pool, 10M to creator.
35
+ function _handleInitialDistribution() internal virtual override {
36
+ // Mint the total supply to the coin contract
37
+ _mint(address(this), CoinConstants.MAX_TOTAL_SUPPLY);
38
+
39
+ // Distribute the creator launch reward to the payout recipient
40
+ _transfer(address(this), payoutRecipient, CoinConstants.CREATOR_LAUNCH_REWARD);
41
+
42
+ // Transfer the market supply to the hook for liquidity
43
+ _transfer(address(this), address(poolKey.hooks), balanceOf(address(this)));
44
+ }
45
+ }
@@ -9,9 +9,11 @@ pragma solidity ^0.8.28;
9
9
 
10
10
  import {ICreatorCoin} from "./interfaces/ICreatorCoin.sol";
11
11
  import {CreatorCoinConstants} from "./libs/CreatorCoinConstants.sol";
12
- import {IHooks, PoolConfiguration, PoolKey, IPoolManager, ICoinV4, CoinV4} from "./CoinV4.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";
13
15
 
14
- contract CreatorCoin is ICreatorCoin, CoinV4 {
16
+ contract CreatorCoin is ICreatorCoin, BaseCoin {
15
17
  uint256 public vestingStartTime;
16
18
  uint256 public vestingEndTime;
17
19
  uint256 public totalClaimed;
@@ -21,7 +23,7 @@ contract CreatorCoin is ICreatorCoin, CoinV4 {
21
23
  address _protocolRewards,
22
24
  IPoolManager _poolManager,
23
25
  address _airlock
24
- ) CoinV4(_protocolRewardRecipient, _protocolRewards, _poolManager, _airlock) initializer {}
26
+ ) BaseCoin(_protocolRewardRecipient, _protocolRewards, _poolManager, _airlock) initializer {}
25
27
 
26
28
  function initialize(
27
29
  address payoutRecipient_,
@@ -34,7 +36,7 @@ contract CreatorCoin is ICreatorCoin, CoinV4 {
34
36
  PoolKey memory poolKey_,
35
37
  uint160 sqrtPriceX96,
36
38
  PoolConfiguration memory poolConfiguration_
37
- ) public override(CoinV4, ICoinV4) {
39
+ ) public override(BaseCoin, ICoin) {
38
40
  require(currency_ == CreatorCoinConstants.CURRENCY, InvalidCurrency());
39
41
 
40
42
  super.initialize(payoutRecipient_, owners_, tokenURI_, name_, symbol_, platformReferrer_, currency_, poolKey_, sqrtPriceX96, poolConfiguration_);
@@ -44,7 +46,7 @@ contract CreatorCoin is ICreatorCoin, CoinV4 {
44
46
  }
45
47
 
46
48
  /// @dev The initial mint and distribution of the coin supply.
47
- /// Overrides the CoinV4._handleInitialDistribution to transfer the market supply to the hook.
49
+ /// Implements creator coin specific distribution: 500M to liquidity pool, 500M vested to creator.
48
50
  function _handleInitialDistribution() internal override {
49
51
  _mint(address(this), CreatorCoinConstants.TOTAL_SUPPLY);
50
52
 
@@ -8,7 +8,7 @@
8
8
  pragma solidity ^0.8.23;
9
9
 
10
10
  import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
11
- import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
11
+ import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
12
12
  import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
13
13
  import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
14
14
  import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
@@ -20,18 +20,13 @@ import {IWETH} from "./interfaces/IWETH.sol";
20
20
  import {IZoraFactory} from "./interfaces/IZoraFactory.sol";
21
21
  import {IHasAfterCoinDeploy} from "./hooks/deployment/BaseCoinDeployHook.sol";
22
22
  import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
23
- import {Coin} from "./Coin.sol";
24
- import {CoinV4} from "./CoinV4.sol";
25
23
  import {ICoin, PoolKeyStruct} from "./interfaces/ICoin.sol";
26
- import {ICoinV3} from "./interfaces/ICoinV3.sol";
27
- import {ICoinV4} from "./interfaces/ICoinV4.sol";
24
+ import {ICoin} from "./interfaces/ICoin.sol";
28
25
  import {IHasContractName} from "@zoralabs/shared-contracts/interfaces/IContractMetadata.sol";
29
26
  import {ContractVersionBase} from "./version/ContractVersionBase.sol";
30
27
  import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
31
28
  import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
32
29
  import {CoinCommon} from "./libs/CoinCommon.sol";
33
- import {UniV3Config} from "./libs/CoinSetupV3.sol";
34
- import {CoinSetupV3} from "./libs/CoinSetupV3.sol";
35
30
  import {PoolConfiguration} from "./types/PoolConfiguration.sol";
36
31
  import {LpPosition} from "./types/LpPosition.sol";
37
32
  import {IVersionedContract} from "@zoralabs/shared-contracts/interfaces/IVersionedContract.sol";
@@ -45,7 +40,7 @@ contract ZoraFactoryImpl is
45
40
  IZoraFactory,
46
41
  UUPSUpgradeable,
47
42
  ReentrancyGuardUpgradeable,
48
- OwnableUpgradeable,
43
+ Ownable2StepUpgradeable,
49
44
  IHasContractName,
50
45
  ContractVersionBase,
51
46
  DeployedCoinVersionLookup
@@ -53,16 +48,14 @@ contract ZoraFactoryImpl is
53
48
  using SafeERC20 for IERC20;
54
49
 
55
50
  /// @notice The coin contract implementation address
56
- address public immutable coinImpl;
57
51
  address public immutable coinV4Impl;
58
52
  address public immutable creatorCoinImpl;
59
53
  address public immutable contentCoinHook;
60
54
  address public immutable creatorCoinHook;
61
55
 
62
- constructor(address _coinImpl, address _coinV4Impl, address _creatorCoinImpl, address _contentCoinHook, address _creatorCoinHook) {
56
+ constructor(address _coinV4Impl, address _creatorCoinImpl, address _contentCoinHook, address _creatorCoinHook) {
63
57
  _disableInitializers();
64
58
 
65
- coinImpl = _coinImpl;
66
59
  coinV4Impl = _coinV4Impl;
67
60
  creatorCoinImpl = _creatorCoinImpl;
68
61
  contentCoinHook = _contentCoinHook;
@@ -180,13 +173,13 @@ contract ZoraFactoryImpl is
180
173
  string memory symbol,
181
174
  bytes memory poolConfig,
182
175
  address platformReferrer,
183
- uint256 orderSize
176
+ uint256 /*orderSize*/
184
177
  ) public payable nonReentrant returns (address, uint256) {
185
178
  bytes32 salt = _randomSalt(payoutRecipient, uri, bytes32(0));
186
179
 
187
180
  ICoin coin = _createAndInitializeCoin(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, salt);
188
181
 
189
- uint256 coinsPurchased = _handleFirstOrder(coin, orderSize);
182
+ uint256 coinsPurchased = 0;
190
183
 
191
184
  return (address(coin), coinsPurchased);
192
185
  }
@@ -225,15 +218,11 @@ contract ZoraFactoryImpl is
225
218
 
226
219
  ICoin coin = _createAndInitializeCoin(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, salt);
227
220
 
228
- uint256 coinsPurchased = _handleFirstOrder(coin, orderSize);
229
-
230
- return (address(coin), coinsPurchased);
221
+ return (address(coin), 0);
231
222
  }
232
223
 
233
224
  function getCoinImpl(uint8 version) internal view returns (address) {
234
- if (CoinConfigurationVersions.isV3(version)) {
235
- return coinImpl;
236
- } else if (CoinConfigurationVersions.isV4(version)) {
225
+ if (CoinConfigurationVersions.isV4(version)) {
237
226
  return coinV4Impl;
238
227
  }
239
228
 
@@ -244,44 +233,8 @@ contract ZoraFactoryImpl is
244
233
  return payable(Clones.cloneDeterministic(getCoinImpl(version), salt));
245
234
  }
246
235
 
247
- function _setupV3Coin(
248
- ICoinV3 coin,
249
- address currency,
250
- bool isCoinToken0,
251
- uint160 sqrtPriceX96,
252
- PoolConfiguration memory poolConfiguration,
253
- address payoutRecipient,
254
- address[] memory owners,
255
- string memory uri,
256
- string memory name,
257
- string memory symbol,
258
- address platformReferrer
259
- ) internal {
260
- address v3Factory = coin.v3Factory();
261
-
262
- address poolAddress = CoinSetupV3.createV3Pool(address(coin), currency, isCoinToken0, sqrtPriceX96, v3Factory);
263
-
264
- LpPosition[] memory positions = CoinDopplerMultiCurve.calculatePositions(isCoinToken0, poolConfiguration, MarketConstants.POOL_LAUNCH_SUPPLY);
265
-
266
- // Initialize coin with pre-configured pool
267
- coin.initialize(payoutRecipient, owners, uri, name, symbol, platformReferrer, currency, poolAddress, poolConfiguration, positions);
268
-
269
- emit CoinCreated(
270
- msg.sender,
271
- payoutRecipient,
272
- platformReferrer,
273
- currency,
274
- uri,
275
- name,
276
- symbol,
277
- address(coin),
278
- poolAddress,
279
- IVersionedContract(address(coin)).contractVersion()
280
- );
281
- }
282
-
283
236
  function _setupV4Coin(
284
- ICoinV4 coin,
237
+ ICoin coin,
285
238
  address currency,
286
239
  bool isCoinToken0,
287
240
  uint160 sqrtPriceX96,
@@ -335,9 +288,10 @@ contract ZoraFactoryImpl is
335
288
  );
336
289
 
337
290
  if (CoinConfigurationVersions.isV3(version)) {
338
- _setupV3Coin(ICoinV3(coin), currency, isCoinToken0, sqrtPriceX96, poolConfiguration, payoutRecipient, owners, uri, name, symbol, platformReferrer);
291
+ // V3 is no longer supported
292
+ revert ICoin.InvalidPoolVersion();
339
293
  } else if (CoinConfigurationVersions.isV4(version)) {
340
- _setupV4Coin(ICoinV4(coin), currency, isCoinToken0, sqrtPriceX96, poolConfiguration, payoutRecipient, owners, uri, name, symbol, platformReferrer);
294
+ _setupV4Coin(ICoin(coin), currency, isCoinToken0, sqrtPriceX96, poolConfiguration, payoutRecipient, owners, uri, name, symbol, platformReferrer);
341
295
  } else {
342
296
  revert ICoin.InvalidPoolVersion();
343
297
  }
@@ -375,43 +329,6 @@ contract ZoraFactoryImpl is
375
329
  );
376
330
  }
377
331
 
378
- /// @dev Handles the first buy of a newly created coin
379
- /// @param coin The newly created coin contract
380
- /// @param orderSize The size of the first buy order; must match msg.value for ETH/WETH pairs
381
- function _handleFirstOrder(ICoin coin, uint256 orderSize) internal returns (uint256 coinsPurchased) {
382
- if (msg.value > 0 || orderSize > 0) {
383
- address currency = coin.currency();
384
- address payoutRecipient = coin.payoutRecipient();
385
-
386
- if (currency != Coin(payable(address(coin))).WETH()) {
387
- if (msg.value != 0) {
388
- revert EthTransferInvalid();
389
- }
390
-
391
- _handleIncomingCurrency(currency, orderSize);
392
-
393
- IERC20(currency).approve(address(coin), orderSize);
394
-
395
- (, coinsPurchased) = Coin(payable(address(coin))).buy(payoutRecipient, orderSize, 0, 0, address(0));
396
- } else {
397
- (, coinsPurchased) = Coin(payable(address(coin))).buy{value: msg.value}(payoutRecipient, orderSize, 0, 0, address(0));
398
- }
399
- }
400
- }
401
-
402
- /// @dev Safely transfers ERC20 tokens from the caller to this contract to be sent to the newly created coin
403
- /// @param currency The ERC20 token address to transfer
404
- /// @param orderSize The amount of tokens to transfer for the order
405
- function _handleIncomingCurrency(address currency, uint256 orderSize) internal {
406
- uint256 beforeBalance = IERC20(currency).balanceOf(address(this));
407
- IERC20(currency).safeTransferFrom(msg.sender, address(this), orderSize);
408
- uint256 afterBalance = IERC20(currency).balanceOf(address(this));
409
-
410
- if ((afterBalance - beforeBalance) != orderSize) {
411
- revert ERC20TransferAmountMismatch();
412
- }
413
- }
414
-
415
332
  /// @notice Initializes the factory proxy contract
416
333
  /// @param initialOwner Address of the contract owner
417
334
  /// @dev Can only be called once due to initializer modifier