@zoralabs/coins 0.9.0 → 1.0.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 (124) hide show
  1. package/.turbo/turbo-build.log +131 -114
  2. package/CHANGELOG.md +40 -0
  3. package/abis/BaseCoin.json +26 -118
  4. package/abis/BaseTest.json +47 -0
  5. package/abis/Coin.json +171 -63
  6. package/abis/CoinDopplerMultiCurve.json +38 -0
  7. package/abis/CoinRewardsV4.json +54 -0
  8. package/abis/CoinTest.json +53 -20
  9. package/abis/CoinUniV4Test.json +1053 -0
  10. package/abis/CoinV4.json +234 -211
  11. package/abis/DeployScript.json +47 -0
  12. package/abis/DeployedCoinVersionLookup.json +21 -0
  13. package/abis/DeployedCoinVersionLookupTest.json +716 -0
  14. package/abis/DifferentNamespaceVersionLookup.json +39 -0
  15. package/abis/DopplerUniswapV3Test.json +49 -93
  16. package/abis/ERC20.json +310 -0
  17. package/abis/FactoryTest.json +85 -7
  18. package/abis/FeeEstimatorHook.json +1528 -0
  19. package/abis/HooksDeployment.json +23 -0
  20. package/abis/HooksTest.json +47 -0
  21. package/abis/ICoin.json +40 -71
  22. package/abis/ICoinV3.json +879 -0
  23. package/abis/ICoinV4.json +915 -0
  24. package/abis/IDeployedCoinVersionLookup.json +21 -0
  25. package/abis/IERC721.json +36 -36
  26. package/abis/IHasPoolKey.json +42 -0
  27. package/abis/IHasRewardsRecipients.json +54 -0
  28. package/abis/IHasSwapPath.json +60 -0
  29. package/abis/IMsgSender.json +15 -0
  30. package/abis/IPoolConfigEncoding.json +46 -0
  31. package/abis/ISwapPathRouter.json +92 -0
  32. package/abis/IUniversalRouter.json +61 -0
  33. package/abis/IUnlockCallback.json +21 -0
  34. package/abis/IV4Quoter.json +310 -0
  35. package/abis/IZoraFactory.json +191 -11
  36. package/abis/IZoraV4CoinHook.json +348 -4
  37. package/abis/MockERC20.json +21 -0
  38. package/abis/MultiOwnableTest.json +47 -0
  39. package/abis/{CoinConfigurationVersions.json → Position.json} +1 -1
  40. package/abis/PrintUpgradeCommand.json +9 -0
  41. package/abis/ProxyShim.json +24 -0
  42. package/abis/StateLibrary.json +80 -0
  43. package/abis/TestDeployedCoinVersionLookupImplementation.json +39 -0
  44. package/abis/TestV4Swap.json +9 -0
  45. package/abis/UpgradeCoinImpl.json +47 -0
  46. package/abis/UpgradesTest.json +67 -0
  47. package/abis/Vm.json +1482 -111
  48. package/abis/VmSafe.json +856 -32
  49. package/abis/ZoraFactoryImpl.json +339 -1
  50. package/abis/ZoraV4CoinHook.json +455 -5
  51. package/addresses/8453.json +8 -4
  52. package/addresses/84532.json +8 -4
  53. package/dist/index.cjs +1920 -169
  54. package/dist/index.cjs.map +1 -1
  55. package/dist/index.js +1916 -169
  56. package/dist/index.js.map +1 -1
  57. package/dist/wagmiGenerated.d.ts +2599 -183
  58. package/dist/wagmiGenerated.d.ts.map +1 -1
  59. package/package/wagmiGenerated.ts +1928 -165
  60. package/package.json +8 -3
  61. package/remappings.txt +6 -1
  62. package/script/CoinsDeployerBase.sol +74 -11
  63. package/script/DeployDevFactory.s.sol +21 -0
  64. package/script/PrintUpgradeCommand.s.sol +13 -0
  65. package/script/Simulate.s.sol +1 -10
  66. package/script/TestBackingCoinSwap.s.sol +146 -0
  67. package/script/TestV4Swap.s.sol +136 -0
  68. package/script/UpgradeFactoryImpl.s.sol +1 -1
  69. package/src/BaseCoin.sol +176 -0
  70. package/src/Coin.sol +87 -202
  71. package/src/CoinV4.sol +121 -0
  72. package/src/ZoraFactoryImpl.sol +208 -36
  73. package/src/hooks/ZoraV4CoinHook.sol +195 -0
  74. package/src/hooks/{BaseCoinDeployHook.sol → deployment/BaseCoinDeployHook.sol} +3 -3
  75. package/src/hooks/{BuySupplyWithSwapRouterHook.sol → deployment/BuySupplyWithSwapRouterHook.sol} +7 -5
  76. package/src/interfaces/ICoin.sol +31 -39
  77. package/src/interfaces/ICoinV3.sol +71 -0
  78. package/src/interfaces/ICoinV4.sol +69 -0
  79. package/src/interfaces/IDeployedCoinVersionLookup.sol +11 -0
  80. package/src/interfaces/IMsgSender.sol +9 -0
  81. package/src/interfaces/IPoolConfigEncoding.sol +14 -0
  82. package/src/interfaces/ISwapPathRouter.sol +14 -0
  83. package/src/interfaces/IZoraFactory.sol +65 -27
  84. package/src/interfaces/IZoraV4CoinHook.sol +116 -0
  85. package/src/libs/CoinCommon.sol +15 -0
  86. package/src/libs/CoinConfigurationVersions.sol +116 -1
  87. package/src/libs/CoinConstants.sol +5 -0
  88. package/src/libs/CoinDopplerMultiCurve.sol +134 -0
  89. package/src/libs/CoinDopplerUniV3.sol +19 -171
  90. package/src/libs/CoinRewards.sol +195 -0
  91. package/src/libs/CoinRewardsV4.sol +180 -0
  92. package/src/libs/CoinSetup.sol +57 -0
  93. package/src/libs/CoinSetupV3.sol +6 -67
  94. package/src/libs/DopplerMath.sol +156 -0
  95. package/src/libs/HooksDeployment.sol +84 -0
  96. package/src/libs/MarketConstants.sol +4 -0
  97. package/src/libs/PoolStateReader.sol +22 -0
  98. package/src/libs/UniV3BuySell.sol +74 -292
  99. package/src/libs/UniV4SwapHelper.sol +65 -0
  100. package/src/libs/UniV4SwapToCurrency.sol +109 -0
  101. package/src/libs/V4Liquidity.sol +129 -0
  102. package/src/types/PoolConfiguration.sol +15 -0
  103. package/src/utils/DeployedCoinVersionLookup.sol +52 -0
  104. package/src/version/ContractVersionBase.sol +1 -1
  105. package/test/Coin.t.sol +78 -88
  106. package/test/CoinDopplerUniV3.t.sol +32 -171
  107. package/test/CoinUniV4.t.sol +752 -0
  108. package/test/{Hooks.t.sol → DeploymentHooks.t.sol} +2 -6
  109. package/test/Factory.t.sol +80 -47
  110. package/test/MultiOwnable.t.sol +6 -3
  111. package/test/Upgrades.t.sol +6 -5
  112. package/test/mocks/MockERC20.sol +12 -0
  113. package/test/utils/BaseTest.sol +106 -56
  114. package/test/utils/DeployedCoinVersionLookup.t.sol +127 -0
  115. package/test/utils/FeeEstimatorHook.sol +84 -0
  116. package/test/utils/ProxyShim.sol +17 -0
  117. package/wagmi.config.ts +4 -0
  118. package/.env +0 -1
  119. package/.turbo/turbo-update-contract-version.log +0 -22
  120. package/abis/CoinSetupV3.json +0 -7
  121. package/abis/HookDeployer.json +0 -68
  122. package/abis/IHookDeployer.json +0 -42
  123. package/src/libs/CoinLegacy.sol +0 -48
  124. package/src/libs/CoinLegacyMarket.sol +0 -182
@@ -0,0 +1,195 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5
+ import {Address} from "@openzeppelin/contracts/utils/Address.sol";
6
+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
7
+ import {IProtocolRewards} from "../interfaces/IProtocolRewards.sol";
8
+ import {ICoin} from "../interfaces/ICoin.sol";
9
+ import {CoinConstants} from "./CoinConstants.sol";
10
+ import {IWETH} from "../interfaces/IWETH.sol";
11
+
12
+ struct CoinConfig {
13
+ address protocolRewardRecipient;
14
+ address platformReferrer;
15
+ address payoutRecipient;
16
+ address protocolRewards;
17
+ }
18
+
19
+ library CoinRewards {
20
+ using SafeERC20 for IERC20;
21
+
22
+ /// @dev Handles sending ETH and ERC20 payouts and refunds to recipients
23
+ /// @param orderPayout The amount of currency to pay out
24
+ /// @param recipient The address to receive the payout
25
+ function handlePayout(uint256 orderPayout, address recipient, address currency, address weth) internal {
26
+ if (currency == weth) {
27
+ Address.sendValue(payable(recipient), orderPayout);
28
+ } else {
29
+ IERC20(currency).safeTransfer(recipient, orderPayout);
30
+ }
31
+ }
32
+
33
+ /// @dev Handles calculating and depositing fees to an escrow protocol rewards contract
34
+ function handleTradeRewards(uint256 totalValue, address _tradeReferrer, CoinConfig memory coinConfig, address currency, IWETH weth) internal {
35
+ address protocolRewardRecipient = coinConfig.protocolRewardRecipient;
36
+ address platformReferrer = coinConfig.platformReferrer;
37
+ address payoutRecipient = coinConfig.payoutRecipient;
38
+ IProtocolRewards protocolRewards = IProtocolRewards(coinConfig.protocolRewards);
39
+
40
+ if (_tradeReferrer == address(0)) {
41
+ _tradeReferrer = protocolRewardRecipient;
42
+ }
43
+
44
+ uint256 tokenCreatorFee = calculateReward(totalValue, CoinConstants.TOKEN_CREATOR_FEE_BPS);
45
+ uint256 platformReferrerFee = calculateReward(totalValue, CoinConstants.PLATFORM_REFERRER_FEE_BPS);
46
+ uint256 tradeReferrerFee = calculateReward(totalValue, CoinConstants.TRADE_REFERRER_FEE_BPS);
47
+ uint256 protocolFee = totalValue - tokenCreatorFee - platformReferrerFee - tradeReferrerFee;
48
+
49
+ if (currency == address(weth)) {
50
+ address[] memory recipients = new address[](4);
51
+ uint256[] memory amounts = new uint256[](4);
52
+ bytes4[] memory reasons = new bytes4[](4);
53
+
54
+ recipients[0] = payoutRecipient;
55
+ amounts[0] = tokenCreatorFee;
56
+ reasons[0] = bytes4(keccak256("COIN_CREATOR_REWARD"));
57
+
58
+ recipients[1] = platformReferrer;
59
+ amounts[1] = platformReferrerFee;
60
+ reasons[1] = bytes4(keccak256("COIN_PLATFORM_REFERRER_REWARD"));
61
+
62
+ recipients[2] = _tradeReferrer;
63
+ amounts[2] = tradeReferrerFee;
64
+ reasons[2] = bytes4(keccak256("COIN_TRADE_REFERRER_REWARD"));
65
+
66
+ recipients[3] = protocolRewardRecipient;
67
+ amounts[3] = protocolFee;
68
+ reasons[3] = bytes4(keccak256("COIN_PROTOCOL_REWARD"));
69
+
70
+ IProtocolRewards(protocolRewards).depositBatch{value: totalValue}(recipients, amounts, reasons, "");
71
+ }
72
+
73
+ if (currency != address(weth)) {
74
+ IERC20(currency).safeTransfer(payoutRecipient, tokenCreatorFee);
75
+ IERC20(currency).safeTransfer(platformReferrer, platformReferrerFee);
76
+ IERC20(currency).safeTransfer(_tradeReferrer, tradeReferrerFee);
77
+ IERC20(currency).safeTransfer(protocolRewardRecipient, protocolFee);
78
+ }
79
+
80
+ emit ICoin.CoinTradeRewards(
81
+ payoutRecipient,
82
+ platformReferrer,
83
+ _tradeReferrer,
84
+ protocolRewardRecipient,
85
+ tokenCreatorFee,
86
+ platformReferrerFee,
87
+ tradeReferrerFee,
88
+ protocolFee,
89
+ currency
90
+ );
91
+ }
92
+
93
+ function calculateReward(uint256 amount, uint256 bps) internal pure returns (uint256) {
94
+ return (amount * bps) / 10_000;
95
+ }
96
+
97
+ function transferBothRewards(
98
+ address token0,
99
+ uint256 totalAmountToken0,
100
+ address token1,
101
+ uint256 totalAmountToken1,
102
+ address coin,
103
+ CoinConfig memory coinConfig,
104
+ address currency,
105
+ IWETH weth,
106
+ address doppler
107
+ ) internal returns (ICoin.MarketRewards memory rewards) {
108
+ rewards = transferMarketRewards(token0, currency, totalAmountToken0, rewards, coin, coinConfig, weth, doppler);
109
+ rewards = transferMarketRewards(token1, currency, totalAmountToken1, rewards, coin, coinConfig, weth, doppler);
110
+ }
111
+
112
+ struct Distribution {
113
+ bool isCurrency;
114
+ uint256 totalAmount;
115
+ uint256 creatorPayout;
116
+ uint256 platformReferrerPayout;
117
+ uint256 protocolPayout;
118
+ }
119
+
120
+ function transferMarketRewards(
121
+ address token,
122
+ address currency,
123
+ uint256 totalAmount,
124
+ ICoin.MarketRewards memory rewards,
125
+ address coin,
126
+ CoinConfig memory coinConfig,
127
+ IWETH weth,
128
+ address dopplerRecipient
129
+ ) internal returns (ICoin.MarketRewards memory) {
130
+ address payoutRecipient = coinConfig.payoutRecipient;
131
+ address platformReferrer = coinConfig.platformReferrer;
132
+ address protocolRewardRecipient = coinConfig.protocolRewardRecipient;
133
+ address protocolRewards = coinConfig.protocolRewards;
134
+
135
+ if (totalAmount > 0) {
136
+ uint256 dopplerPayout = calculateReward(totalAmount, CoinConstants.DOPPLER_MARKET_REWARD_BPS);
137
+ uint256 creatorPayout = calculateReward(totalAmount, CoinConstants.CREATOR_MARKET_REWARD_BPS);
138
+ uint256 platformReferrerPayout = calculateReward(totalAmount, CoinConstants.PLATFORM_REFERRER_MARKET_REWARD_BPS);
139
+ uint256 protocolPayout = totalAmount - creatorPayout - platformReferrerPayout - dopplerPayout;
140
+
141
+ bool isCurrency = token == currency;
142
+
143
+ if (token == address(weth)) {
144
+ IWETH(weth).withdraw(totalAmount);
145
+
146
+ address[] memory recipients = new address[](4);
147
+ recipients[0] = payoutRecipient;
148
+ recipients[1] = platformReferrer;
149
+ recipients[2] = protocolRewardRecipient;
150
+ recipients[3] = dopplerRecipient;
151
+
152
+ uint256[] memory amounts = new uint256[](4);
153
+ amounts[0] = creatorPayout;
154
+ amounts[1] = platformReferrerPayout;
155
+ amounts[2] = protocolPayout;
156
+ amounts[3] = dopplerPayout;
157
+
158
+ bytes4[] memory reasons = new bytes4[](4);
159
+ reasons[0] = bytes4(keccak256("COIN_CREATOR_MARKET_REWARD"));
160
+ reasons[1] = bytes4(keccak256("COIN_PLATFORM_REFERRER_MARKET_REWARD"));
161
+ reasons[2] = bytes4(keccak256("COIN_PROTOCOL_MARKET_REWARD"));
162
+ reasons[3] = bytes4(keccak256("COIN_DOPPLER_MARKET_REWARD"));
163
+
164
+ IProtocolRewards(protocolRewards).depositBatch{value: totalAmount}(recipients, amounts, reasons, "");
165
+ IProtocolRewards(protocolRewards).withdrawFor(dopplerRecipient, dopplerPayout);
166
+ } else {
167
+ if (!isCurrency) {
168
+ IERC20(coin).safeTransfer(payoutRecipient, creatorPayout);
169
+ IERC20(coin).safeTransfer(platformReferrer, platformReferrerPayout);
170
+ IERC20(coin).safeTransfer(protocolRewardRecipient, protocolPayout);
171
+ IERC20(coin).safeTransfer(dopplerRecipient, dopplerPayout);
172
+ } else {
173
+ IERC20(currency).safeTransfer(payoutRecipient, creatorPayout);
174
+ IERC20(currency).safeTransfer(platformReferrer, platformReferrerPayout);
175
+ IERC20(currency).safeTransfer(protocolRewardRecipient, protocolPayout);
176
+ IERC20(currency).safeTransfer(dopplerRecipient, dopplerPayout);
177
+ }
178
+ }
179
+
180
+ if (isCurrency) {
181
+ rewards.totalAmountCurrency = totalAmount;
182
+ rewards.creatorPayoutAmountCurrency = creatorPayout;
183
+ rewards.platformReferrerAmountCurrency = platformReferrerPayout;
184
+ rewards.protocolAmountCurrency = protocolPayout;
185
+ } else {
186
+ rewards.totalAmountCoin = totalAmount;
187
+ rewards.creatorPayoutAmountCoin = creatorPayout;
188
+ rewards.platformReferrerAmountCoin = platformReferrerPayout;
189
+ rewards.protocolAmountCoin = protocolPayout;
190
+ }
191
+ }
192
+
193
+ return rewards;
194
+ }
195
+ }
@@ -0,0 +1,180 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.28;
3
+
4
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
5
+ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
6
+ import {LpPosition} from "../types/LpPosition.sol";
7
+ import {V4Liquidity} from "./V4Liquidity.sol";
8
+ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
9
+ import {IHasRewardsRecipients} from "../interfaces/ICoin.sol";
10
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
11
+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
12
+ import {ICoin} from "../interfaces/ICoin.sol";
13
+ import {IZoraV4CoinHook} from "../interfaces/IZoraV4CoinHook.sol";
14
+ import {UniV4SwapToCurrency} from "./UniV4SwapToCurrency.sol";
15
+ import {IHasSwapPath} from "../interfaces/ICoinV4.sol";
16
+
17
+ library CoinRewardsV4 {
18
+ using SafeERC20 for IERC20;
19
+
20
+ // creator gets 50% of the total fee
21
+ uint256 public constant CREATOR_REWARD_BPS = 5000;
22
+
23
+ // create referrer gets 15% of the total fee
24
+ uint256 public constant CREATE_REFERRAL_REWARD_BPS = 1500;
25
+
26
+ // trade referrer gets 10% of the total fee
27
+ uint256 public constant TRADE_REFERRAL_REWARD_BPS = 1500;
28
+
29
+ // doppler gets 5% of the total fee
30
+ uint256 public constant DOPPLER_REWARD_BPS = 500;
31
+
32
+ function getTradeReferral(bytes calldata hookData) internal pure returns (address) {
33
+ return hookData.length > 0 ? abi.decode(hookData, (address)) : address(0);
34
+ }
35
+
36
+ /// @notice Collects fees from LP positions, swaps them to target payout currency, and transfers to hook contract, so
37
+ /// that they can later be distributed as rewards.
38
+ /// @param poolManager The pool manager instance
39
+ /// @param key The pool key
40
+ /// @param positions The LP positions to collect fees from
41
+ /// @param payoutSwapPath The swap path to convert fees to target currency
42
+ /// @return fees0 The amount of fees collected in currency0
43
+ /// @return fees1 The amount of fees collected in currency1
44
+ /// @return receivedCurrency The final currency after swapping
45
+ /// @return receivedAmount The final amount after swapping
46
+ function collectFeesAndConvertToPayout(
47
+ IPoolManager poolManager,
48
+ PoolKey memory key,
49
+ LpPosition[] storage positions,
50
+ IHasSwapPath.PayoutSwapPath memory payoutSwapPath
51
+ ) internal returns (int128 fees0, int128 fees1, Currency receivedCurrency, uint128 receivedAmount) {
52
+ // Step 1: Collect accrued fees from all LP positions in both token0 and token1
53
+ (fees0, fees1) = V4Liquidity.collectFees(poolManager, key, positions);
54
+
55
+ // Step 2: Swap the collected fees through the specified path to convert them to the target payout currency
56
+ // This handles multi-hop swaps if needed (e.g. coin -> backingCoin -> backingCoin's currency)
57
+ (receivedCurrency, receivedAmount) = UniV4SwapToCurrency.swapToPath(
58
+ poolManager,
59
+ uint128(fees0),
60
+ uint128(fees1),
61
+ payoutSwapPath.currencyIn,
62
+ payoutSwapPath.path
63
+ );
64
+
65
+ // Step 3: Transfer the final converted currency amount to this contract for distribution
66
+ // This makes the tokens available for the subsequent reward distribution
67
+ if (receivedAmount > 0) {
68
+ poolManager.take(receivedCurrency, address(this), receivedAmount);
69
+ }
70
+ }
71
+
72
+ /// @notice Distributes collected market fees as rewards to various recipients including creator, referrers, protocol, and doppler
73
+ /// @dev Calculates reward amounts based on predefined basis points and transfers the specified currency to each recipient
74
+ /// @param currency The currency token to distribute as rewards (can be native ETH if address is zero)
75
+ /// @param fees The total amount of fees collected to be distributed
76
+ /// @param coin The coin contract instance that implements IHasRewardsRecipients to get recipient addresses
77
+ /// @param tradeReferrer The address of the trade referrer who should receive trade referral rewards (can be zero address)
78
+ function distributeMarketRewards(Currency currency, uint128 fees, IHasRewardsRecipients coin, address tradeReferrer) internal {
79
+ address payoutRecipient = coin.payoutRecipient();
80
+ address platformReferrer = coin.platformReferrer();
81
+ address protocolRewardRecipient = coin.protocolRewardRecipient();
82
+ address doppler = coin.dopplerFeeRecipient();
83
+
84
+ MarketRewards memory rewards = _distributeCurrencyRewards(
85
+ currency,
86
+ fees,
87
+ payoutRecipient,
88
+ platformReferrer,
89
+ protocolRewardRecipient,
90
+ doppler,
91
+ tradeReferrer
92
+ );
93
+
94
+ IZoraV4CoinHook.MarketRewardsV4 memory marketRewards = IZoraV4CoinHook.MarketRewardsV4({
95
+ creatorPayoutAmountCurrency: rewards.creatorAmount,
96
+ creatorPayoutAmountCoin: 0,
97
+ platformReferrerAmountCurrency: rewards.platformReferrerAmount,
98
+ platformReferrerAmountCoin: 0,
99
+ tradeReferrerAmountCurrency: rewards.tradeReferrerAmount,
100
+ tradeReferrerAmountCoin: 0,
101
+ protocolAmountCurrency: rewards.protocolAmount,
102
+ protocolAmountCoin: 0,
103
+ dopplerAmountCurrency: rewards.dopplerAmount,
104
+ dopplerAmountCoin: 0
105
+ });
106
+
107
+ emit IZoraV4CoinHook.CoinMarketRewardsV4(
108
+ address(coin),
109
+ Currency.unwrap(currency),
110
+ payoutRecipient,
111
+ platformReferrer,
112
+ tradeReferrer,
113
+ protocolRewardRecipient,
114
+ doppler,
115
+ marketRewards
116
+ );
117
+ }
118
+
119
+ struct MarketRewards {
120
+ uint256 platformReferrerAmount;
121
+ uint256 tradeReferrerAmount;
122
+ uint256 protocolAmount;
123
+ uint256 creatorAmount;
124
+ uint256 dopplerAmount;
125
+ }
126
+
127
+ function _distributeCurrencyRewards(
128
+ Currency currency,
129
+ uint128 fee,
130
+ address payoutRecipient,
131
+ address platformReferrer,
132
+ address protocolRewardRecipient,
133
+ address doppler,
134
+ address tradeReferral
135
+ ) internal returns (MarketRewards memory rewards) {
136
+ rewards = _computeMarketRewards(fee, tradeReferral != address(0), platformReferrer != address(0));
137
+
138
+ if (platformReferrer != address(0)) {
139
+ _transferCurrency(currency, rewards.platformReferrerAmount, platformReferrer);
140
+ }
141
+ if (tradeReferral != address(0)) {
142
+ _transferCurrency(currency, rewards.tradeReferrerAmount, tradeReferral);
143
+ }
144
+ _transferCurrency(currency, rewards.creatorAmount, payoutRecipient);
145
+ _transferCurrency(currency, rewards.dopplerAmount, doppler);
146
+ _transferCurrency(currency, rewards.protocolAmount, protocolRewardRecipient);
147
+ }
148
+
149
+ function _transferCurrency(Currency currency, uint256 amount, address to) internal {
150
+ if (amount == 0) {
151
+ return;
152
+ }
153
+
154
+ if (currency.isAddressZero()) {
155
+ (bool success, ) = payable(to).call{value: amount}("");
156
+ if (!success) {
157
+ revert ICoin.EthTransferFailed();
158
+ }
159
+ } else {
160
+ IERC20(Currency.unwrap(currency)).safeTransfer(to, amount);
161
+ }
162
+ }
163
+
164
+ function _computeMarketRewards(uint128 fee, bool hasTradeReferral, bool hasCreateReferral) internal pure returns (MarketRewards memory rewards) {
165
+ if (fee == 0) {
166
+ return rewards;
167
+ }
168
+
169
+ uint256 totalAmount = uint256(fee);
170
+ rewards.platformReferrerAmount = hasCreateReferral ? calculateReward(totalAmount, CREATE_REFERRAL_REWARD_BPS) : 0;
171
+ rewards.tradeReferrerAmount = hasTradeReferral ? calculateReward(totalAmount, TRADE_REFERRAL_REWARD_BPS) : 0;
172
+ rewards.creatorAmount = calculateReward(totalAmount, CREATOR_REWARD_BPS);
173
+ rewards.dopplerAmount = calculateReward(totalAmount, DOPPLER_REWARD_BPS);
174
+ rewards.protocolAmount = totalAmount - rewards.platformReferrerAmount - rewards.tradeReferrerAmount - rewards.creatorAmount - rewards.dopplerAmount;
175
+ }
176
+
177
+ function calculateReward(uint256 amount, uint256 bps) internal pure returns (uint256) {
178
+ return (amount * bps) / 10_000;
179
+ }
180
+ }
@@ -0,0 +1,57 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {PoolConfigurationV4} from "../interfaces/ICoin.sol";
5
+ import {CoinConfigurationVersions} from "./CoinConfigurationVersions.sol";
6
+ import {ICoin} from "../interfaces/ICoin.sol";
7
+ import {CoinCommon} from "./CoinCommon.sol";
8
+ import {MarketConstants} from "./MarketConstants.sol";
9
+ import {TickMath} from "../utils/uniswap/TickMath.sol";
10
+ import {IPoolManager, PoolKey, Currency, IHooks} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
11
+ import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
12
+ import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
13
+ import {MarketConstants} from "./MarketConstants.sol";
14
+ import {LpPosition} from "../types/LpPosition.sol";
15
+ import {CoinDopplerMultiCurve, PoolConfiguration} from "./CoinDopplerMultiCurve.sol";
16
+ import {CoinDopplerUniV3} from "./CoinDopplerUniV3.sol";
17
+
18
+ library CoinSetup {
19
+ function generatePoolConfig(
20
+ address coin,
21
+ bytes memory poolConfig_
22
+ ) internal pure returns (uint8 version, address currency, uint160 sqrtPriceX96, bool isCoinToken0, PoolConfiguration memory poolConfiguration) {
23
+ // Extract version and currency from pool config
24
+ (version, currency) = CoinConfigurationVersions.decodeVersionAndCurrency(poolConfig_);
25
+
26
+ isCoinToken0 = CoinCommon.sortTokens(coin, currency);
27
+
28
+ (sqrtPriceX96, poolConfiguration) = setupPoolWithVersion(version, poolConfig_, isCoinToken0);
29
+ }
30
+
31
+ function buildPoolKey(address coin, address currency, bool isCoinToken0, IHooks hooks) internal pure returns (PoolKey memory poolKey) {
32
+ Currency currency0 = isCoinToken0 ? Currency.wrap(coin) : Currency.wrap(currency);
33
+ Currency currency1 = isCoinToken0 ? Currency.wrap(currency) : Currency.wrap(coin);
34
+
35
+ poolKey = PoolKey({
36
+ currency0: currency0,
37
+ currency1: currency1,
38
+ fee: MarketConstants.LP_FEE_V4,
39
+ tickSpacing: MarketConstants.TICK_SPACING,
40
+ hooks: hooks
41
+ });
42
+ }
43
+
44
+ function setupPoolWithVersion(
45
+ uint8 version,
46
+ bytes memory poolConfig_,
47
+ bool isCoinToken0
48
+ ) internal pure returns (uint160 sqrtPriceX96, PoolConfiguration memory poolConfiguration) {
49
+ if (version == CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION) {
50
+ (sqrtPriceX96, poolConfiguration) = CoinDopplerUniV3.setupPool(isCoinToken0, poolConfig_);
51
+ } else if (version == CoinConfigurationVersions.DOPPLER_MULTICURVE_UNI_V4_POOL_VERSION) {
52
+ (sqrtPriceX96, poolConfiguration) = CoinDopplerMultiCurve.setupPool(isCoinToken0, poolConfig_);
53
+ } else {
54
+ revert ICoin.InvalidPoolVersion();
55
+ }
56
+ }
57
+ }
@@ -2,14 +2,14 @@
2
2
  pragma solidity ^0.8.23;
3
3
 
4
4
  import {PoolConfiguration} from "../interfaces/ICoin.sol";
5
- import {CoinLegacy} from "./CoinLegacy.sol";
6
5
  import {CoinDopplerUniV3} from "./CoinDopplerUniV3.sol";
7
6
  import {CoinConfigurationVersions} from "./CoinConfigurationVersions.sol";
8
7
  import {LpPosition} from "../types/LpPosition.sol";
9
8
  import {IUniswapV3Factory} from "../interfaces/IUniswapV3Factory.sol";
10
9
  import {MarketConstants} from "./MarketConstants.sol";
11
10
  import {IUniswapV3Pool} from "../interfaces/IUniswapV3Pool.sol";
12
-
11
+ import {ICoin} from "../interfaces/ICoin.sol";
12
+ import {CoinCommon} from "./CoinCommon.sol";
13
13
  struct UniV3Config {
14
14
  address weth;
15
15
  address v3Factory;
@@ -24,75 +24,12 @@ struct CoinV3Config {
24
24
  }
25
25
 
26
26
  library CoinSetupV3 {
27
- error InvalidPoolVersion();
28
-
29
- function setupPool(
30
- bytes memory poolConfig_,
31
- UniV3Config memory uniswapV3Config,
32
- address coin
33
- ) internal returns (address currency, address poolAddress, PoolConfiguration memory poolConfiguration) {
34
- // Extract version and currency from pool config
35
- (uint8 version, address currency_) = abi.decode(poolConfig_, (uint8, address));
36
-
37
- // Store the currency, defaulting to WETH if address(0)
38
- currency = currency_ == address(0) ? uniswapV3Config.weth : currency_;
39
-
40
- // Sort token addresses for Uniswap V3 pool creation
41
- bool isCoinToken0 = _sortTokens(coin, currency);
42
- address token0 = isCoinToken0 ? coin : currency;
43
- address token1 = isCoinToken0 ? currency : coin;
44
-
45
- // Configure the pool with appropriate version
46
- uint160 sqrtPriceX96;
47
- (sqrtPriceX96, poolConfiguration) = setupPoolWithVersion(version, poolConfig_, isCoinToken0, uniswapV3Config.weth);
48
-
49
- // Create the pool
50
- poolAddress = _createPool(token0, token1, sqrtPriceX96, uniswapV3Config.v3Factory);
51
- }
52
-
53
27
  /// @dev Deploys the Uniswap V3 pool and mints initial liquidity based on the pool configuration
54
- function deployLiquidity(address coin, address currency, PoolConfiguration memory poolConfiguration, address poolAddress) internal {
28
+ function deployLiquidity(LpPosition[] memory positions, address poolAddress) internal {
55
29
  // Calculate and mint positions
56
- LpPosition[] memory positions = calculatePositions(coin, currency, poolConfiguration);
57
30
  _mintPositions(positions, poolAddress);
58
31
  }
59
32
 
60
- // Helper function to sort tokens and determine if coin is token0
61
- function _sortTokens(address coin, address currency) private pure returns (bool isCoinToken0) {
62
- return coin < currency;
63
- }
64
-
65
- function setupPoolWithVersion(
66
- uint8 version,
67
- bytes memory poolConfig_,
68
- bool isCoinToken0,
69
- address weth
70
- ) internal pure returns (uint160 sqrtPriceX96, PoolConfiguration memory poolConfiguration) {
71
- if (version == CoinConfigurationVersions.LEGACY_POOL_VERSION) {
72
- (sqrtPriceX96, poolConfiguration) = CoinLegacy.setupPool(isCoinToken0, poolConfig_, weth);
73
- } else if (version == CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION) {
74
- (sqrtPriceX96, poolConfiguration) = CoinDopplerUniV3.setupPool(isCoinToken0, poolConfig_);
75
- } else {
76
- revert InvalidPoolVersion();
77
- }
78
- }
79
-
80
- function calculatePositions(
81
- address coin,
82
- address currency,
83
- PoolConfiguration memory poolConfiguration
84
- ) internal pure returns (LpPosition[] memory positions) {
85
- // Create the pool
86
- bool isCoinToken0 = _sortTokens(coin, currency);
87
- if (poolConfiguration.version == CoinConfigurationVersions.LEGACY_POOL_VERSION) {
88
- positions = CoinLegacy.calculatePositions(isCoinToken0, poolConfiguration.tickLower, poolConfiguration.tickUpper);
89
- } else if (poolConfiguration.version == CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION) {
90
- positions = CoinDopplerUniV3.calculatePositions(isCoinToken0, poolConfiguration);
91
- } else {
92
- revert InvalidPoolVersion();
93
- }
94
- }
95
-
96
33
  /// @dev Mints the calculated liquidity positions into the Uniswap V3 pool
97
34
  function _mintPositions(LpPosition[] memory lbpPositions, address poolAddress) internal {
98
35
  for (uint256 i; i < lbpPositions.length; i++) {
@@ -101,7 +38,9 @@ library CoinSetupV3 {
101
38
  }
102
39
 
103
40
  /// @dev Creates the Uniswap V3 pool for the coin/currency pair
104
- function _createPool(address token0, address token1, uint160 sqrtPriceX96, address v3Factory) internal returns (address pool) {
41
+ function createV3Pool(address coin, address currency, bool isCoinToken0, uint160 sqrtPriceX96, address v3Factory) internal returns (address pool) {
42
+ address token0 = isCoinToken0 ? coin : currency;
43
+ address token1 = isCoinToken0 ? currency : coin;
105
44
  pool = IUniswapV3Factory(v3Factory).createPool(token0, token1, MarketConstants.LP_FEE);
106
45
 
107
46
  // This pool should be new, if it has already been initialized