@zoralabs/coins 0.7.1 → 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 (167) hide show
  1. package/.turbo/turbo-build.log +106 -84
  2. package/CHANGELOG.md +68 -0
  3. package/abis/BadImpl.json +15 -0
  4. package/abis/BalanceDeltaLibrary.json +15 -0
  5. package/abis/BaseCoin.json +1350 -0
  6. package/abis/BaseCoinDeployHook.json +78 -0
  7. package/abis/BaseHook.json +897 -0
  8. package/abis/BaseTest.json +60 -91
  9. package/abis/BeforeSwapDeltaLibrary.json +15 -0
  10. package/abis/BuySupplyWithSwapRouterHook.json +126 -0
  11. package/abis/Coin.json +214 -150
  12. package/abis/CoinConstants.json +65 -0
  13. package/abis/CoinDopplerMultiCurve.json +38 -0
  14. package/abis/CoinRewardsV4.json +54 -0
  15. package/abis/CoinTest.json +66 -111
  16. package/abis/CoinUniV4Test.json +1053 -0
  17. package/abis/CoinV4.json +1687 -0
  18. package/abis/CurrencyLibrary.json +25 -0
  19. package/abis/DeployHooks.json +9 -0
  20. package/abis/DeployScript.json +47 -0
  21. package/abis/DeployedCoinVersionLookup.json +21 -0
  22. package/abis/DeployedCoinVersionLookupTest.json +716 -0
  23. package/abis/DifferentNamespaceVersionLookup.json +39 -0
  24. package/abis/DopplerUniswapV3Test.json +62 -184
  25. package/abis/ERC20.json +310 -0
  26. package/abis/FactoryTest.json +98 -98
  27. package/abis/FakeHookNoInterface.json +21 -0
  28. package/abis/FeeEstimatorHook.json +1528 -0
  29. package/abis/Hooks.json +28 -0
  30. package/abis/HooksDeployment.json +23 -0
  31. package/abis/HooksTest.json +698 -0
  32. package/abis/IAllowanceTransfer.json +486 -0
  33. package/abis/ICoin.json +62 -69
  34. package/abis/ICoinDeployHook.json +31 -0
  35. package/abis/ICoinV3.json +879 -0
  36. package/abis/ICoinV4.json +915 -0
  37. package/abis/IContractMetadata.json +28 -0
  38. package/abis/IDeployedCoinVersionLookup.json +21 -0
  39. package/abis/IEIP712.json +15 -0
  40. package/abis/IEIP712_v4.json +15 -0
  41. package/abis/IERC20Minimal.json +172 -0
  42. package/abis/IERC6909Claims.json +288 -0
  43. package/abis/IERC721.json +36 -36
  44. package/abis/IERC721Permit_v4.json +88 -0
  45. package/abis/IExtsload.json +64 -0
  46. package/abis/IExttload.json +40 -0
  47. package/abis/IHasAfterCoinDeploy.json +31 -0
  48. package/abis/IHasContractName.json +15 -0
  49. package/abis/IHasPoolKey.json +42 -0
  50. package/abis/IHasRewardsRecipients.json +54 -0
  51. package/abis/IHasSwapPath.json +60 -0
  52. package/abis/IHooks.json +789 -0
  53. package/abis/IImmutableState.json +15 -0
  54. package/abis/IMsgSender.json +15 -0
  55. package/abis/IMulticall_v4.json +21 -0
  56. package/abis/INotifier.json +187 -0
  57. package/abis/IPermit2.json +865 -0
  58. package/abis/IPermit2Forwarder.json +138 -0
  59. package/abis/IPoolConfigEncoding.json +46 -0
  60. package/abis/IPoolInitializer_v4.json +53 -0
  61. package/abis/IPoolManager.json +1286 -0
  62. package/abis/IPositionManager.json +712 -0
  63. package/abis/IProtocolFees.json +174 -0
  64. package/abis/ISignatureTransfer.json +394 -0
  65. package/abis/ISubscriber.json +89 -0
  66. package/abis/ISwapPathRouter.json +92 -0
  67. package/abis/ISwapRouter.json +82 -0
  68. package/abis/IUniversalRouter.json +61 -0
  69. package/abis/IUnlockCallback.json +21 -0
  70. package/abis/IUnorderedNonce.json +44 -0
  71. package/abis/IV4Quoter.json +310 -0
  72. package/abis/IV4Router.json +47 -0
  73. package/abis/IZoraFactory.json +328 -4
  74. package/abis/IZoraV4CoinHook.json +427 -0
  75. package/abis/ImmutableState.json +36 -0
  76. package/abis/LPFeeLibrary.json +65 -0
  77. package/abis/MockERC20.json +21 -0
  78. package/abis/MultiOwnableTest.json +60 -91
  79. package/abis/{CoinConfigurationVersions.json → Position.json} +1 -1
  80. package/abis/PrintUpgradeCommand.json +9 -0
  81. package/abis/ProxyShim.json +24 -0
  82. package/abis/Simulate.json +0 -91
  83. package/abis/StateLibrary.json +80 -0
  84. package/abis/TestDeployedCoinVersionLookupImplementation.json +39 -0
  85. package/abis/TestV4Swap.json +9 -0
  86. package/abis/{CoinSetup.json → UniV3BuySell.json} +5 -0
  87. package/abis/UniV3Errors.json +32 -0
  88. package/abis/UpgradeCoinImpl.json +47 -0
  89. package/abis/UpgradeFactoryImpl.json +9 -0
  90. package/abis/UpgradesTest.json +671 -0
  91. package/abis/Vm.json +1482 -111
  92. package/abis/VmSafe.json +856 -32
  93. package/abis/ZoraFactoryImpl.json +450 -1
  94. package/abis/ZoraV4CoinHook.json +1439 -0
  95. package/addresses/8453.json +8 -3
  96. package/addresses/84532.json +8 -3
  97. package/dist/index.cjs +1998 -184
  98. package/dist/index.cjs.map +1 -1
  99. package/dist/index.js +1989 -178
  100. package/dist/index.js.map +1 -1
  101. package/dist/wagmiGenerated.d.ts +2852 -688
  102. package/dist/wagmiGenerated.d.ts.map +1 -1
  103. package/package/wagmiGenerated.ts +1992 -173
  104. package/package.json +7 -2
  105. package/remappings.txt +6 -1
  106. package/script/CoinsDeployerBase.sol +105 -10
  107. package/script/DeployDevFactory.s.sol +21 -0
  108. package/script/DeployHooks.s.sol +22 -0
  109. package/script/PrintUpgradeCommand.s.sol +13 -0
  110. package/script/Simulate.s.sol +4 -12
  111. package/script/TestBackingCoinSwap.s.sol +146 -0
  112. package/script/TestV4Swap.s.sol +136 -0
  113. package/script/UpgradeCoinImpl.sol +2 -2
  114. package/script/UpgradeFactoryImpl.s.sol +23 -0
  115. package/src/BaseCoin.sol +176 -0
  116. package/src/Coin.sol +93 -515
  117. package/src/CoinV4.sol +121 -0
  118. package/src/ZoraFactoryImpl.sol +257 -57
  119. package/src/hooks/ZoraV4CoinHook.sol +195 -0
  120. package/src/hooks/deployment/BaseCoinDeployHook.sol +62 -0
  121. package/src/hooks/deployment/BuySupplyWithSwapRouterHook.sol +80 -0
  122. package/src/interfaces/ICoin.sol +35 -39
  123. package/src/interfaces/ICoinDeployHook.sol +8 -0
  124. package/src/interfaces/ICoinV3.sol +71 -0
  125. package/src/interfaces/ICoinV4.sol +69 -0
  126. package/src/interfaces/IDeployedCoinVersionLookup.sol +11 -0
  127. package/src/interfaces/IMsgSender.sol +9 -0
  128. package/src/interfaces/IPoolConfigEncoding.sol +14 -0
  129. package/src/interfaces/ISwapPathRouter.sol +14 -0
  130. package/src/interfaces/ISwapRouter.sol +1 -35
  131. package/src/interfaces/IZoraFactory.sol +97 -7
  132. package/src/interfaces/IZoraV4CoinHook.sol +116 -0
  133. package/src/libs/CoinCommon.sol +15 -0
  134. package/src/libs/CoinConfigurationVersions.sol +116 -1
  135. package/src/{utils → libs}/CoinConstants.sol +11 -6
  136. package/src/libs/CoinDopplerMultiCurve.sol +134 -0
  137. package/src/libs/CoinDopplerUniV3.sol +19 -171
  138. package/src/libs/CoinRewards.sol +195 -0
  139. package/src/libs/CoinRewardsV4.sol +180 -0
  140. package/src/libs/CoinSetup.sol +40 -20
  141. package/src/libs/CoinSetupV3.sol +50 -0
  142. package/src/libs/DopplerMath.sol +156 -0
  143. package/src/libs/HooksDeployment.sol +84 -0
  144. package/src/libs/MarketConstants.sol +4 -0
  145. package/src/libs/PoolStateReader.sol +22 -0
  146. package/src/libs/UniV3BuySell.sol +231 -0
  147. package/src/libs/UniV3Errors.sol +11 -0
  148. package/src/libs/UniV4SwapHelper.sol +65 -0
  149. package/src/libs/UniV4SwapToCurrency.sol +109 -0
  150. package/src/libs/V4Liquidity.sol +129 -0
  151. package/src/types/PoolConfiguration.sol +15 -0
  152. package/src/utils/DeployedCoinVersionLookup.sol +52 -0
  153. package/src/version/ContractVersionBase.sol +1 -1
  154. package/test/Coin.t.sol +94 -101
  155. package/test/CoinDopplerUniV3.t.sol +35 -184
  156. package/test/CoinUniV4.t.sol +752 -0
  157. package/test/DeploymentHooks.t.sol +270 -0
  158. package/test/Factory.t.sol +84 -50
  159. package/test/MultiOwnable.t.sol +6 -3
  160. package/test/Upgrades.t.sol +68 -0
  161. package/test/mocks/MockERC20.sol +12 -0
  162. package/test/utils/BaseTest.sol +124 -59
  163. package/test/utils/DeployedCoinVersionLookup.t.sol +127 -0
  164. package/test/utils/FeeEstimatorHook.sol +84 -0
  165. package/test/utils/ProxyShim.sol +17 -0
  166. package/wagmi.config.ts +10 -9
  167. package/src/libs/CoinLegacy.sol +0 -48
package/src/Coin.sol CHANGED
@@ -2,10 +2,7 @@
2
2
  pragma solidity ^0.8.23;
3
3
 
4
4
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5
- import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
6
5
  import {ICoin, PoolConfiguration} from "./interfaces/ICoin.sol";
7
- import {ICoinComments} from "./interfaces/ICoinComments.sol";
8
- import {IERC7572} from "./interfaces/IERC7572.sol";
9
6
  import {IUniswapV3Factory} from "./interfaces/IUniswapV3Factory.sol";
10
7
  import {IUniswapV3Pool} from "./interfaces/IUniswapV3Pool.sol";
11
8
  import {ISwapRouter} from "./interfaces/ISwapRouter.sol";
@@ -18,15 +15,15 @@ import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/
18
15
  import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
19
16
  import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
20
17
  import {ContractVersionBase} from "./version/ContractVersionBase.sol";
21
- import {CoinConstants} from "./utils/CoinConstants.sol";
22
18
  import {MultiOwnable} from "./utils/MultiOwnable.sol";
23
- import {FullMath} from "./utils/uniswap/FullMath.sol";
24
- import {TickMath} from "./utils/uniswap/TickMath.sol";
25
- import {LiquidityAmounts} from "./utils/uniswap/LiquidityAmounts.sol";
26
- import {CoinSetup} from "./libs/CoinSetup.sol";
19
+ import {CoinConstants} from "./libs/CoinConstants.sol";
27
20
  import {MarketConstants} from "./libs/MarketConstants.sol";
28
21
  import {LpPosition} from "./types/LpPosition.sol";
29
22
  import {PoolState} from "./types/PoolState.sol";
23
+ import {CoinSetupV3, UniV3Config, CoinV3Config} from "./libs/CoinSetupV3.sol";
24
+ import {UniV3BuySell, CoinConfig, SellResult} from "./libs/UniV3BuySell.sol";
25
+ import {BaseCoin} from "./BaseCoin.sol";
26
+ import {ICoinV3} from "./interfaces/ICoinV3.sol";
30
27
 
31
28
  /*
32
29
  $$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\
@@ -38,158 +35,96 @@ import {PoolState} from "./types/PoolState.sol";
38
35
  \$$$$$$ | $$$$$$ |$$$$$$\ $$ | \$$ |
39
36
  \______/ \______/ \______|\__| \__|
40
37
  */
41
- contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeable, MultiOwnable, ReentrancyGuardUpgradeable {
38
+ contract Coin is BaseCoin, ICoinV3 {
42
39
  using SafeERC20 for IERC20;
43
40
 
44
- /// @notice The address of the WETH contract
45
- address public immutable WETH;
46
- /// @notice The address of the Uniswap V3 factory
47
41
  address public immutable v3Factory;
48
42
  /// @notice The address of the Uniswap V3 swap router
49
43
  address public immutable swapRouter;
50
- /// @notice The address of the Airlock contract, ownership is used for a protocol fee split.
51
- address public immutable airlock;
52
- /// @notice The address of the protocol rewards contract
53
- address public immutable protocolRewards;
54
- /// @notice The address of the protocol reward recipient
55
- address public immutable protocolRewardRecipient;
56
-
57
- /// @notice The metadata URI
58
- string public tokenURI;
59
- /// @notice The address of the coin creator
60
- address public payoutRecipient;
61
- /// @notice The address of the platform referrer
62
- address public platformReferrer;
63
44
  /// @notice The address of the Uniswap V3 pool
64
45
  address public poolAddress;
65
- /// @notice The address of the currency
66
- address public currency;
67
46
 
47
+ /// @notice The state of the market
48
+ bytes public market;
49
+ uint8 public marketVersion;
50
+
51
+ /// @notice The address of the WETH contract
52
+ address public immutable WETH;
53
+
54
+ /// @notice deprecated
68
55
  PoolConfiguration public poolConfiguration;
69
56
 
70
- /// @notice Returns the state of the pool
71
- /// @dev This is a legacy function for compatibility with doppler default state
72
- /// @return asset The address of the asset
73
- /// @return numeraire The address of the numeraire
74
- /// @return tickLower The lower tick
75
- /// @return tickUpper The upper tick
76
- /// @return numPositions The number of discovery positions
77
- /// @return isInitialized Whether the pool is initialized
78
- /// @return isExited Whether the pool is exited
79
- /// @return maxShareToBeSold The maximum share to be sold
80
- /// @return totalTokensOnBondingCurve The total tokens on the bonding curve
81
- function poolState()
82
- external
83
- view
84
- returns (
85
- address asset,
86
- address numeraire,
87
- int24 tickLower,
88
- int24 tickUpper,
89
- uint16 numPositions,
90
- bool isInitialized,
91
- bool isExited,
92
- uint256 maxShareToBeSold,
93
- uint256 totalTokensOnBondingCurve
94
- )
95
- {
96
- asset = address(this);
97
- numeraire = currency;
98
- tickLower = poolConfiguration.tickLower;
99
- tickUpper = poolConfiguration.tickUpper;
100
- numPositions = poolConfiguration.numPositions;
101
- isInitialized = true;
102
- isExited = false;
103
- maxShareToBeSold = poolConfiguration.maxDiscoverySupplyShare;
104
- totalTokensOnBondingCurve = POOL_LAUNCH_SUPPLY;
105
- }
57
+ LpPosition[] public positions;
106
58
 
107
59
  /**
108
60
  * @notice The constructor for the static Coin contract deployment shared across all Coins.
109
- * @param _protocolRewardRecipient The address of the protocol reward recipient
110
- * @param _protocolRewards The address of the protocol rewards contract
111
- * @param _weth The address of the WETH contract
112
- * @param _v3Factory The address of the Uniswap V3 factory
113
- * @param _swapRouter The address of the Uniswap V3 swap router
114
- * @param _airlock The address of the Airlock contract, ownership is used for a protocol fee split.
61
+ * @param protocolRewardRecipient_ The address of the protocol reward recipient
62
+ * @param protocolRewards_ The address of the protocol rewards contract
63
+ * @param weth_ The address of the WETH contract
64
+ * @param v3Factory_ The address of the Uniswap V3 factory
65
+ * @param swapRouter_ The address of the Uniswap V3 swap router
66
+ * @param airlock_ The address of the Airlock contract, ownership is used for a protocol fee split.
115
67
  */
116
68
  constructor(
117
- address _protocolRewardRecipient,
118
- address _protocolRewards,
119
- address _weth,
120
- address _v3Factory,
121
- address _swapRouter,
122
- address _airlock
123
- ) initializer {
124
- if (_protocolRewardRecipient == address(0)) {
125
- revert AddressZero();
126
- }
127
- if (_protocolRewards == address(0)) {
128
- revert AddressZero();
129
- }
130
- if (_weth == address(0)) {
69
+ address protocolRewardRecipient_,
70
+ address protocolRewards_,
71
+ address weth_,
72
+ address v3Factory_,
73
+ address swapRouter_,
74
+ address airlock_
75
+ ) BaseCoin(protocolRewardRecipient_, protocolRewards_, airlock_) initializer {
76
+ if (v3Factory_ == address(0)) {
131
77
  revert AddressZero();
132
78
  }
133
- if (_v3Factory == address(0)) {
79
+ if (swapRouter_ == address(0)) {
134
80
  revert AddressZero();
135
81
  }
136
- if (_swapRouter == address(0)) {
82
+ if (airlock_ == address(0)) {
137
83
  revert AddressZero();
138
84
  }
139
- if (_airlock == address(0)) {
85
+ if (weth_ == address(0)) {
140
86
  revert AddressZero();
141
87
  }
88
+ swapRouter = swapRouter_;
89
+ v3Factory = v3Factory_;
142
90
 
143
- protocolRewardRecipient = _protocolRewardRecipient;
144
- protocolRewards = _protocolRewards;
145
- WETH = _weth;
146
- swapRouter = _swapRouter;
147
- v3Factory = _v3Factory;
148
- airlock = _airlock;
91
+ WETH = weth_;
149
92
  }
150
93
 
151
- /// @notice Initializes a new coin
152
- /// @param payoutRecipient_ The address of the coin creator
153
- /// @param tokenURI_ The metadata URI
154
- /// @param name_ The coin name
155
- /// @param symbol_ The coin symbol
156
- /// @param poolConfig_ The parameters for the v3 pool and liquidity
157
- /// @param platformReferrer_ The address of the platform referrer
94
+ /// @inheritdoc ICoinV3
158
95
  function initialize(
159
96
  address payoutRecipient_,
160
97
  address[] memory owners_,
161
98
  string memory tokenURI_,
162
99
  string memory name_,
163
100
  string memory symbol_,
164
- bytes memory poolConfig_,
165
- address platformReferrer_
101
+ address platformReferrer_,
102
+ address currency_,
103
+ address poolAddress_,
104
+ PoolConfiguration memory poolConfiguration_,
105
+ LpPosition[] memory positions_
166
106
  ) public initializer {
167
- // Validate the creation parameters
168
- if (payoutRecipient_ == address(0)) {
169
- revert AddressZero();
170
- }
171
-
172
- // Set base contract state
173
- __ERC20_init(name_, symbol_);
174
- __ERC20Permit_init(name_);
175
- __MultiOwnable_init(owners_);
176
- __ReentrancyGuard_init();
107
+ super._initialize(payoutRecipient_, owners_, tokenURI_, name_, symbol_, platformReferrer_);
177
108
 
178
- // Set mutable state
179
- _setPayoutRecipient(payoutRecipient_);
180
- _setContractURI(tokenURI_);
109
+ currency = currency_;
110
+ poolAddress = poolAddress_;
111
+ poolConfiguration = poolConfiguration_;
112
+ positions = positions_;
181
113
 
182
- // Store the referrer if set
183
- platformReferrer = platformReferrer_ == address(0) ? protocolRewardRecipient : platformReferrer_;
184
-
185
- // Mint the total supply
186
- _mint(address(this), MAX_TOTAL_SUPPLY);
114
+ CoinSetupV3.deployLiquidity(positions_, poolAddress);
115
+ }
187
116
 
188
- // Distribute the creator launch reward
189
- _transfer(address(this), payoutRecipient, CREATOR_LAUNCH_REWARD);
117
+ function buildConfig() internal view returns (CoinConfig memory coinConfig) {
118
+ coinConfig = CoinConfig({
119
+ protocolRewardRecipient: protocolRewardRecipient,
120
+ platformReferrer: platformReferrer,
121
+ payoutRecipient: payoutRecipient,
122
+ protocolRewards: protocolRewards
123
+ });
124
+ }
190
125
 
191
- // Deploy the pool
192
- _deployLiquidity(poolConfig_);
126
+ function getPoolConfiguration() public view returns (PoolConfiguration memory) {
127
+ return poolConfiguration;
193
128
  }
194
129
 
195
130
  /// @notice Executes a buy order
@@ -204,37 +139,20 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
204
139
  uint160 sqrtPriceLimitX96,
205
140
  address tradeReferrer
206
141
  ) public payable nonReentrant returns (uint256, uint256) {
207
- // Ensure the recipient is not the zero address
208
- if (recipient == address(0)) {
209
- revert AddressZero();
210
- }
211
-
212
- // Calculate the trade reward
213
- uint256 tradeReward = _calculateReward(orderSize, TOTAL_FEE_BPS);
214
-
215
- // Calculate the remaining size
216
- uint256 trueOrderSize = orderSize - tradeReward;
217
-
218
- // Handle incoming currency
219
- _handleIncomingCurrency(orderSize, trueOrderSize);
220
-
221
- // Set up the swap parameters
222
- ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
223
- tokenIn: currency,
224
- tokenOut: address(this),
225
- fee: MarketConstants.LP_FEE,
226
- recipient: recipient,
227
- amountIn: trueOrderSize,
228
- amountOutMinimum: minAmountOut,
229
- sqrtPriceLimitX96: sqrtPriceLimitX96
230
- });
231
-
232
- // Execute the swap
233
- uint256 amountOut = ISwapRouter(swapRouter).exactInputSingle(params);
234
-
235
- _handleTradeRewards(tradeReward, tradeReferrer);
142
+ CoinConfig memory coinConfig = buildConfig();
143
+ (uint256 amountOut, uint256 tradeReward, uint256 trueOrderSize) = UniV3BuySell.handleBuy(
144
+ recipient,
145
+ orderSize,
146
+ minAmountOut,
147
+ sqrtPriceLimitX96,
148
+ tradeReferrer,
149
+ coinConfig,
150
+ currency,
151
+ ISwapRouter(swapRouter),
152
+ IWETH(WETH)
153
+ );
236
154
 
237
- _handleMarketRewards();
155
+ UniV3BuySell.handleMarketRewards(coinConfig, currency, poolAddress, positions, IWETH(WETH), dopplerFeeRecipient());
238
156
 
239
157
  emit CoinBuy(msg.sender, recipient, tradeReferrer, amountOut, currency, tradeReward, trueOrderSize);
240
158
 
@@ -254,11 +172,6 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
254
172
  uint160 sqrtPriceLimitX96,
255
173
  address tradeReferrer
256
174
  ) public nonReentrant returns (uint256, uint256) {
257
- // Ensure the recipient is not the zero address
258
- if (recipient == address(0)) {
259
- revert AddressZero();
260
- }
261
-
262
175
  // Record the coin balance of this contract before the swap
263
176
  uint256 beforeCoinBalance = balanceOf(address(this));
264
177
 
@@ -268,69 +181,33 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
268
181
  // Approve the Uniswap V3 swap router
269
182
  this.approve(swapRouter, orderSize);
270
183
 
271
- // Set the swap parameters
272
- ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
273
- tokenIn: address(this),
274
- tokenOut: currency,
275
- fee: MarketConstants.LP_FEE,
276
- recipient: address(this),
277
- amountIn: orderSize,
278
- amountOutMinimum: minAmountOut,
279
- sqrtPriceLimitX96: sqrtPriceLimitX96
280
- });
281
-
282
- // Execute the swap
283
- uint256 amountOut = ISwapRouter(swapRouter).exactInputSingle(params);
284
-
285
- // Record the coin balance of this contract after the swap
286
- uint256 afterCoinBalance = balanceOf(address(this));
287
-
288
- // If the swap was partially executed:
289
- if (afterCoinBalance > beforeCoinBalance) {
290
- // Calculate the refund
291
- uint256 coinRefund = afterCoinBalance - beforeCoinBalance;
292
-
293
- // Update the order size
294
- orderSize -= coinRefund;
295
-
296
- // Transfer the refund back to the seller
297
- _transfer(address(this), recipient, coinRefund);
298
- }
299
-
300
- // If currency is WETH, convert to ETH
301
- if (currency == WETH) {
302
- IWETH(WETH).withdraw(amountOut);
303
- }
304
-
305
- // Calculate the trade reward
306
- uint256 tradeReward = _calculateReward(amountOut, TOTAL_FEE_BPS);
307
-
308
- // Calculate the payout after the fee
309
- uint256 payoutSize = amountOut - tradeReward;
310
-
311
- _handlePayout(payoutSize, recipient);
312
-
313
- _handleTradeRewards(tradeReward, tradeReferrer);
314
-
315
- _handleMarketRewards();
184
+ CoinConfig memory coinConfig = buildConfig();
185
+
186
+ SellResult memory result = UniV3BuySell.handleSell(
187
+ recipient,
188
+ beforeCoinBalance,
189
+ orderSize,
190
+ minAmountOut,
191
+ sqrtPriceLimitX96,
192
+ tradeReferrer,
193
+ coinConfig,
194
+ currency,
195
+ ISwapRouter(swapRouter),
196
+ IWETH(WETH)
197
+ );
316
198
 
317
- emit CoinSell(msg.sender, recipient, tradeReferrer, orderSize, currency, tradeReward, payoutSize);
199
+ UniV3BuySell.handleMarketRewards(coinConfig, currency, poolAddress, positions, IWETH(WETH), dopplerFeeRecipient());
318
200
 
319
- return (orderSize, payoutSize);
320
- }
201
+ emit ICoin.CoinSell(msg.sender, recipient, tradeReferrer, result.trueOrderSize, currency, result.tradeReward, result.payoutSize);
321
202
 
322
- /// @notice Enables a user to burn their tokens
323
- /// @param amount The amount of tokens to burn
324
- function burn(uint256 amount) external {
325
- // This burn function sets the from as msg.sender, so having an unauthed call is safe.
326
- _burn(msg.sender, amount);
203
+ return (result.trueOrderSize, result.payoutSize);
327
204
  }
328
205
 
329
206
  /// @notice Force claim any accrued secondary rewards from the market's liquidity position.
330
207
  /// @dev This function is a fallback, secondary rewards will be claimed automatically on each buy and sell.
331
208
  /// @param pushEthRewards Whether to push the ETH directly to the recipients.
332
209
  function claimSecondaryRewards(bool pushEthRewards) external nonReentrant {
333
- MarketRewards memory rewards = _handleMarketRewards();
210
+ MarketRewards memory rewards = UniV3BuySell.handleMarketRewards(buildConfig(), currency, poolAddress, positions, IWETH(WETH), dopplerFeeRecipient());
334
211
 
335
212
  if (pushEthRewards && rewards.totalAmountCurrency > 0 && currency == WETH) {
336
213
  IProtocolRewards(protocolRewards).withdrawFor(payoutRecipient, rewards.creatorPayoutAmountCurrency);
@@ -339,314 +216,15 @@ contract Coin is ICoin, CoinConstants, ContractVersionBase, ERC20PermitUpgradeab
339
216
  }
340
217
  }
341
218
 
342
- /// @notice Set the creator's payout address
343
- /// @param newPayoutRecipient The new recipient address
344
- function setPayoutRecipient(address newPayoutRecipient) external onlyOwner {
345
- _setPayoutRecipient(newPayoutRecipient);
346
- }
347
-
348
- /// @notice Set the contract URI
349
- /// @param newURI The new URI
350
- function setContractURI(string memory newURI) external onlyOwner {
351
- _setContractURI(newURI);
352
- }
353
-
354
- /// @notice The contract metadata
355
- function contractURI() external view returns (string memory) {
356
- return tokenURI;
357
- }
358
-
359
- /// @notice ERC165 interface support
360
- /// @param interfaceId The interface ID to check
361
- function supportsInterface(bytes4 interfaceId) public pure virtual returns (bool) {
362
- return
363
- interfaceId == type(ICoin).interfaceId ||
364
- interfaceId == type(ICoinComments).interfaceId ||
365
- interfaceId == type(IERC7572).interfaceId ||
366
- interfaceId == type(IERC165).interfaceId;
367
- }
368
-
369
- /// @notice Receives ETH converted from WETH
370
- receive() external payable {
371
- require(msg.sender == WETH, OnlyWeth());
372
- }
373
-
374
219
  /// @dev Called by the pool after minting liquidity to transfer the associated coins
375
220
  function uniswapV3MintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata) external {
376
- if (msg.sender != poolAddress) revert OnlyPool();
221
+ if (msg.sender != poolAddress) revert OnlyPool(msg.sender, poolAddress);
377
222
 
378
223
  IERC20(address(this)).safeTransfer(poolAddress, amount0Owed == 0 ? amount1Owed : amount0Owed);
379
224
  }
380
225
 
381
- /// @dev Overrides ERC20's _update function to emit a superset `CoinTransfer` event
382
- function _update(address from, address to, uint256 value) internal virtual override {
383
- super._update(from, to, value);
384
-
385
- emit CoinTransfer(from, to, value, balanceOf(from), balanceOf(to));
386
- }
387
-
388
- /// @dev Used to set the payout recipient on coin creation and updates
389
- /// @param newPayoutRecipient The new recipient address
390
- function _setPayoutRecipient(address newPayoutRecipient) internal {
391
- if (newPayoutRecipient == address(0)) {
392
- revert AddressZero();
393
- }
394
-
395
- emit CoinPayoutRecipientUpdated(msg.sender, payoutRecipient, newPayoutRecipient);
396
-
397
- payoutRecipient = newPayoutRecipient;
398
- }
399
-
400
- /// @dev Used to set the contract URI on coin creation and updates
401
- /// @param newURI The new URI
402
- function _setContractURI(string memory newURI) internal {
403
- emit ContractMetadataUpdated(msg.sender, newURI, name());
404
- emit ContractURIUpdated();
405
-
406
- tokenURI = newURI;
407
- }
408
-
409
- /// @dev Deploys the Uniswap V3 pool and mints initial liquidity based on the pool configuration
410
- function _deployLiquidity(bytes memory poolConfig_) internal {
411
- (uint8 version, address currency_) = abi.decode(poolConfig_, (uint8, address));
412
-
413
- // Store the currency, defaulting to WETH if address(0)
414
- currency = currency_ == address(0) ? WETH : currency_;
415
-
416
- // Sort the token addresses
417
- address token0 = address(this) < currency ? address(this) : currency;
418
- address token1 = address(this) < currency ? currency : address(this);
419
- bool isCoinToken0 = token0 == address(this);
420
-
421
- (uint160 sqrtPriceX96, PoolConfiguration memory _poolConfig) = CoinSetup.setupPoolWithVersion(version, poolConfig_, isCoinToken0, WETH);
422
-
423
- poolConfiguration = _poolConfig;
424
-
425
- poolAddress = _createPool(token0, token1, sqrtPriceX96);
426
-
427
- LpPosition[] memory positions = CoinSetup.calculatePositions(isCoinToken0, poolConfiguration);
428
-
429
- _mintPositions(positions);
430
- }
431
-
432
- /// @dev Creates the Uniswap V3 pool for the coin/currency pair
433
- function _createPool(address token0, address token1, uint160 sqrtPriceX96) internal returns (address pool) {
434
- pool = IUniswapV3Factory(v3Factory).createPool(token0, token1, MarketConstants.LP_FEE);
435
-
436
- // This pool should be new, if it has already been initialized
437
- // then we will fail the creation step prompting the user to try again.
438
- IUniswapV3Pool(pool).initialize(sqrtPriceX96);
439
- }
440
-
441
- /// @dev Mints the calculated liquidity positions into the Uniswap V3 pool
442
- function _mintPositions(LpPosition[] memory lbpPositions) internal {
443
- for (uint256 i; i < lbpPositions.length; i++) {
444
- IUniswapV3Pool(poolAddress).mint(address(this), lbpPositions[i].tickLower, lbpPositions[i].tickUpper, lbpPositions[i].liquidity, "");
445
- }
446
- }
447
-
448
- /// @dev Handles incoming currency transfers for buy orders; if WETH is the currency the caller has the option to send native-ETH
449
- /// @param orderSize The total size of the order in the currency
450
- /// @param trueOrderSize The actual amount being used for the swap after fees
451
- function _handleIncomingCurrency(uint256 orderSize, uint256 trueOrderSize) internal {
452
- if (currency == WETH && msg.value > 0) {
453
- if (msg.value != orderSize) {
454
- revert EthAmountMismatch();
455
- }
456
-
457
- if (msg.value < MIN_ORDER_SIZE) {
458
- revert EthAmountTooSmall();
459
- }
460
-
461
- IWETH(WETH).deposit{value: trueOrderSize}();
462
- IWETH(WETH).approve(swapRouter, trueOrderSize);
463
- } else {
464
- // Ensure ETH is not sent with a non-ETH pair
465
- if (msg.value != 0) {
466
- revert EthTransferInvalid();
467
- }
468
-
469
- uint256 beforeBalance = IERC20(currency).balanceOf(address(this));
470
- IERC20(currency).safeTransferFrom(msg.sender, address(this), orderSize);
471
- uint256 afterBalance = IERC20(currency).balanceOf(address(this));
472
-
473
- if ((afterBalance - beforeBalance) != orderSize) {
474
- revert ERC20TransferAmountMismatch();
475
- }
476
-
477
- IERC20(currency).approve(swapRouter, trueOrderSize);
478
- }
479
- }
480
-
481
- /// @dev Handles sending ETH and ERC20 payouts and refunds to recipients
482
- /// @param orderPayout The amount of currency to pay out
483
- /// @param recipient The address to receive the payout
484
- function _handlePayout(uint256 orderPayout, address recipient) internal {
485
- if (currency == WETH) {
486
- Address.sendValue(payable(recipient), orderPayout);
487
- } else {
488
- IERC20(currency).safeTransfer(recipient, orderPayout);
489
- }
490
- }
491
-
492
- /// @dev Handles calculating and depositing fees to an escrow protocol rewards contract
493
- function _handleTradeRewards(uint256 totalValue, address _tradeReferrer) internal {
494
- if (_tradeReferrer == address(0)) {
495
- _tradeReferrer = protocolRewardRecipient;
496
- }
497
-
498
- uint256 tokenCreatorFee = _calculateReward(totalValue, TOKEN_CREATOR_FEE_BPS);
499
- uint256 platformReferrerFee = _calculateReward(totalValue, PLATFORM_REFERRER_FEE_BPS);
500
- uint256 tradeReferrerFee = _calculateReward(totalValue, TRADE_REFERRER_FEE_BPS);
501
- uint256 protocolFee = totalValue - tokenCreatorFee - platformReferrerFee - tradeReferrerFee;
502
-
503
- if (currency == WETH) {
504
- address[] memory recipients = new address[](4);
505
- uint256[] memory amounts = new uint256[](4);
506
- bytes4[] memory reasons = new bytes4[](4);
507
-
508
- recipients[0] = payoutRecipient;
509
- amounts[0] = tokenCreatorFee;
510
- reasons[0] = bytes4(keccak256("COIN_CREATOR_REWARD"));
511
-
512
- recipients[1] = platformReferrer;
513
- amounts[1] = platformReferrerFee;
514
- reasons[1] = bytes4(keccak256("COIN_PLATFORM_REFERRER_REWARD"));
515
-
516
- recipients[2] = _tradeReferrer;
517
- amounts[2] = tradeReferrerFee;
518
- reasons[2] = bytes4(keccak256("COIN_TRADE_REFERRER_REWARD"));
519
-
520
- recipients[3] = protocolRewardRecipient;
521
- amounts[3] = protocolFee;
522
- reasons[3] = bytes4(keccak256("COIN_PROTOCOL_REWARD"));
523
-
524
- IProtocolRewards(protocolRewards).depositBatch{value: totalValue}(recipients, amounts, reasons, "");
525
- }
526
-
527
- if (currency != WETH) {
528
- IERC20(currency).safeTransfer(payoutRecipient, tokenCreatorFee);
529
- IERC20(currency).safeTransfer(platformReferrer, platformReferrerFee);
530
- IERC20(currency).safeTransfer(_tradeReferrer, tradeReferrerFee);
531
- IERC20(currency).safeTransfer(protocolRewardRecipient, protocolFee);
532
- }
533
-
534
- emit CoinTradeRewards(
535
- payoutRecipient,
536
- platformReferrer,
537
- _tradeReferrer,
538
- protocolRewardRecipient,
539
- tokenCreatorFee,
540
- platformReferrerFee,
541
- tradeReferrerFee,
542
- protocolFee,
543
- currency
544
- );
545
- }
546
-
547
- /// @dev Collects and distributes accrued fees from all LP positions
548
- function _handleMarketRewards() internal returns (MarketRewards memory) {
549
- uint256 totalAmountToken0;
550
- uint256 totalAmountToken1;
551
- uint256 amount0;
552
- uint256 amount1;
553
-
554
- bool isCoinToken0 = address(this) < currency;
555
- LpPosition[] memory positions = CoinSetup.calculatePositions(isCoinToken0, poolConfiguration);
556
-
557
- for (uint256 i; i < positions.length; i++) {
558
- // Must burn to update the collect mapping on the pool
559
- IUniswapV3Pool(poolAddress).burn(positions[i].tickLower, positions[i].tickUpper, 0);
560
-
561
- (amount0, amount1) = IUniswapV3Pool(poolAddress).collect(
562
- address(this),
563
- positions[i].tickLower,
564
- positions[i].tickUpper,
565
- type(uint128).max,
566
- type(uint128).max
567
- );
568
-
569
- totalAmountToken0 += amount0;
570
- totalAmountToken1 += amount1;
571
- }
572
-
573
- address token0 = currency < address(this) ? currency : address(this);
574
- address token1 = currency < address(this) ? address(this) : currency;
575
-
576
- MarketRewards memory rewards;
577
-
578
- rewards = _transferMarketRewards(token0, totalAmountToken0, rewards);
579
- rewards = _transferMarketRewards(token1, totalAmountToken1, rewards);
580
-
581
- emit CoinMarketRewards(payoutRecipient, platformReferrer, protocolRewardRecipient, currency, rewards);
582
-
583
- return rewards;
584
- }
585
-
586
- function _transferMarketRewards(address token, uint256 totalAmount, MarketRewards memory rewards) internal returns (MarketRewards memory) {
587
- if (totalAmount > 0) {
588
- address dopplerRecipient = IAirlock(airlock).owner();
589
- uint256 dopplerPayout = _calculateReward(totalAmount, DOPPLER_MARKET_REWARD_BPS);
590
- uint256 creatorPayout = _calculateReward(totalAmount, CREATOR_MARKET_REWARD_BPS);
591
- uint256 platformReferrerPayout = _calculateReward(totalAmount, PLATFORM_REFERRER_MARKET_REWARD_BPS);
592
- uint256 protocolPayout = totalAmount - creatorPayout - platformReferrerPayout - dopplerPayout;
593
-
594
- if (token == WETH) {
595
- IWETH(WETH).withdraw(totalAmount);
596
-
597
- rewards.totalAmountCurrency = totalAmount;
598
- rewards.creatorPayoutAmountCurrency = creatorPayout;
599
- rewards.platformReferrerAmountCurrency = platformReferrerPayout;
600
- rewards.protocolAmountCurrency = protocolPayout;
601
-
602
- address[] memory recipients = new address[](4);
603
- recipients[0] = payoutRecipient;
604
- recipients[1] = platformReferrer;
605
- recipients[2] = protocolRewardRecipient;
606
- recipients[3] = dopplerRecipient;
607
-
608
- uint256[] memory amounts = new uint256[](4);
609
- amounts[0] = rewards.creatorPayoutAmountCurrency;
610
- amounts[1] = rewards.platformReferrerAmountCurrency;
611
- amounts[2] = rewards.protocolAmountCurrency;
612
- amounts[3] = dopplerPayout;
613
-
614
- bytes4[] memory reasons = new bytes4[](4);
615
- reasons[0] = bytes4(keccak256("COIN_CREATOR_MARKET_REWARD"));
616
- reasons[1] = bytes4(keccak256("COIN_PLATFORM_REFERRER_MARKET_REWARD"));
617
- reasons[2] = bytes4(keccak256("COIN_PROTOCOL_MARKET_REWARD"));
618
- reasons[3] = bytes4(keccak256("COIN_DOPPLER_MARKET_REWARD"));
619
-
620
- IProtocolRewards(protocolRewards).depositBatch{value: totalAmount}(recipients, amounts, reasons, "");
621
- IProtocolRewards(protocolRewards).withdrawFor(dopplerRecipient, dopplerPayout);
622
- } else if (token == address(this)) {
623
- rewards.totalAmountCoin = totalAmount;
624
- rewards.creatorPayoutAmountCoin = creatorPayout;
625
- rewards.platformReferrerAmountCoin = platformReferrerPayout;
626
- rewards.protocolAmountCoin = protocolPayout;
627
-
628
- _transfer(address(this), payoutRecipient, rewards.creatorPayoutAmountCoin);
629
- _transfer(address(this), platformReferrer, rewards.platformReferrerAmountCoin);
630
- _transfer(address(this), protocolRewardRecipient, rewards.protocolAmountCoin);
631
- _transfer(address(this), dopplerRecipient, dopplerPayout);
632
- } else {
633
- rewards.totalAmountCurrency = totalAmount;
634
- rewards.creatorPayoutAmountCurrency = creatorPayout;
635
- rewards.platformReferrerAmountCurrency = platformReferrerPayout;
636
- rewards.protocolAmountCurrency = protocolPayout;
637
-
638
- IERC20(currency).safeTransfer(payoutRecipient, creatorPayout);
639
- IERC20(currency).safeTransfer(platformReferrer, platformReferrerPayout);
640
- IERC20(currency).safeTransfer(protocolRewardRecipient, protocolPayout);
641
- IERC20(currency).safeTransfer(dopplerRecipient, dopplerPayout);
642
- }
643
- }
644
-
645
- return rewards;
646
- }
647
-
648
- /// @dev Utility for computing amounts in basis points.
649
- function _calculateReward(uint256 amount, uint256 bps) internal pure returns (uint256) {
650
- return (amount * bps) / 10_000;
226
+ /// @notice Receives ETH converted from WETH
227
+ receive() external payable {
228
+ require(msg.sender == WETH, OnlyWeth());
651
229
  }
652
230
  }