@zoralabs/coins 0.1.1

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 (116) hide show
  1. package/.turbo/turbo-build.log +76 -0
  2. package/CHANGELOG.md +14 -0
  3. package/LICENSE +21 -0
  4. package/abis/Address.json +29 -0
  5. package/abis/BaseTest.json +691 -0
  6. package/abis/Clones.json +7 -0
  7. package/abis/Coin.json +1693 -0
  8. package/abis/CoinConstants.json +93 -0
  9. package/abis/CoinTest.json +998 -0
  10. package/abis/ContextUpgradeable.json +25 -0
  11. package/abis/ContractVersionBase.json +15 -0
  12. package/abis/Deploy.json +29 -0
  13. package/abis/ECDSA.json +29 -0
  14. package/abis/EIP712.json +67 -0
  15. package/abis/EIP712Upgradeable.json +74 -0
  16. package/abis/ERC1967Proxy.json +67 -0
  17. package/abis/ERC1967Utils.json +85 -0
  18. package/abis/ERC20PermitUpgradeable.json +527 -0
  19. package/abis/ERC20Upgradeable.json +333 -0
  20. package/abis/FactoryTest.json +845 -0
  21. package/abis/IBeacon.json +15 -0
  22. package/abis/ICoin.json +541 -0
  23. package/abis/ICoinComments.json +53 -0
  24. package/abis/IERC1155Errors.json +104 -0
  25. package/abis/IERC165.json +21 -0
  26. package/abis/IERC1822Proxiable.json +15 -0
  27. package/abis/IERC20.json +221 -0
  28. package/abis/IERC20Errors.json +88 -0
  29. package/abis/IERC20Metadata.json +224 -0
  30. package/abis/IERC20Permit.json +77 -0
  31. package/abis/IERC5267.json +51 -0
  32. package/abis/IERC721.json +287 -0
  33. package/abis/IERC721Enumerable.json +343 -0
  34. package/abis/IERC721Errors.json +105 -0
  35. package/abis/IERC721Metadata.json +332 -0
  36. package/abis/IERC721Receiver.json +36 -0
  37. package/abis/IERC721TokenReceiver.json +36 -0
  38. package/abis/IERC7572.json +21 -0
  39. package/abis/IMulticall3.json +440 -0
  40. package/abis/INonfungiblePositionManager.json +366 -0
  41. package/abis/IProtocolRewards.json +348 -0
  42. package/abis/ISwapRouter.json +137 -0
  43. package/abis/IUniswapV3Pool.json +148 -0
  44. package/abis/IUniswapV3SwapCallback.json +25 -0
  45. package/abis/IVersionedContract.json +15 -0
  46. package/abis/IWETH.json +118 -0
  47. package/abis/IZoraFactory.json +138 -0
  48. package/abis/Initializable.json +25 -0
  49. package/abis/Math.json +7 -0
  50. package/abis/MockERC20.json +322 -0
  51. package/abis/MockERC721.json +350 -0
  52. package/abis/MultiOwnable.json +171 -0
  53. package/abis/MultiOwnableTest.json +796 -0
  54. package/abis/NoncesUpgradeable.json +60 -0
  55. package/abis/OwnableUpgradeable.json +99 -0
  56. package/abis/ProtocolRewards.json +494 -0
  57. package/abis/Proxy.json +6 -0
  58. package/abis/ReentrancyGuardUpgradeable.json +30 -0
  59. package/abis/SafeERC20.json +34 -0
  60. package/abis/Script.json +15 -0
  61. package/abis/ShortStrings.json +18 -0
  62. package/abis/StdAssertions.json +379 -0
  63. package/abis/StdInvariant.json +180 -0
  64. package/abis/Strings.json +18 -0
  65. package/abis/Test.json +570 -0
  66. package/abis/UUPSUpgradeable.json +130 -0
  67. package/abis/Vm.json +8627 -0
  68. package/abis/VmSafe.json +7297 -0
  69. package/abis/ZoraFactory.json +67 -0
  70. package/abis/ZoraFactoryImpl.json +422 -0
  71. package/abis/stdError.json +119 -0
  72. package/abis/stdStorageSafe.json +52 -0
  73. package/addresses/1.json +4 -0
  74. package/addresses/8453.json +9 -0
  75. package/addresses/84532.json +9 -0
  76. package/dist/index.cjs +1236 -0
  77. package/dist/index.cjs.map +1 -0
  78. package/dist/index.d.ts +2 -0
  79. package/dist/index.d.ts.map +1 -0
  80. package/dist/index.js +1208 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/wagmiGenerated.d.ts +1645 -0
  83. package/dist/wagmiGenerated.d.ts.map +1 -0
  84. package/foundry.toml +9 -0
  85. package/package/index.ts +1 -0
  86. package/package/wagmiGenerated.ts +1211 -0
  87. package/package.json +48 -0
  88. package/remappings.txt +4 -0
  89. package/script/Deploy.s.sol +14 -0
  90. package/slither.config.json +6 -0
  91. package/src/Coin.sol +579 -0
  92. package/src/ZoraFactoryImpl.sol +142 -0
  93. package/src/interfaces/ICoin.sol +194 -0
  94. package/src/interfaces/ICoinComments.sol +8 -0
  95. package/src/interfaces/IERC7572.sol +12 -0
  96. package/src/interfaces/INonfungiblePositionManager.sol +104 -0
  97. package/src/interfaces/IProtocolRewards.sol +12 -0
  98. package/src/interfaces/ISwapRouter.sol +38 -0
  99. package/src/interfaces/IUniswapV3Pool.sol +43 -0
  100. package/src/interfaces/IUniswapV3SwapCallback.sol +17 -0
  101. package/src/interfaces/IWETH.sol +16 -0
  102. package/src/interfaces/IZoraFactory.sol +56 -0
  103. package/src/proxy/ZoraFactory.sol +26 -0
  104. package/src/utils/CoinConstants.sol +67 -0
  105. package/src/utils/MultiOwnable.sol +126 -0
  106. package/src/utils/TickMath.sol +210 -0
  107. package/src/version/ContractVersionBase.sol +14 -0
  108. package/test/Coin.t.sol +443 -0
  109. package/test/Factory.t.sol +298 -0
  110. package/test/MultiOwnable.t.sol +156 -0
  111. package/test/utils/BaseTest.sol +178 -0
  112. package/test/utils/ProtocolRewards.sol +1499 -0
  113. package/tsconfig.build.json +10 -0
  114. package/tsconfig.json +9 -0
  115. package/tsup.config.ts +11 -0
  116. package/wagmi.config.ts +16 -0
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@zoralabs/coins",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "sideEffects": false,
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "default": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "dependencies": {
17
+ "@openzeppelin/contracts": "^5.0.2",
18
+ "@openzeppelin/contracts-upgradeable": "^5.0.2"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^20.1.2",
22
+ "@wagmi/cli": "^1.0.1",
23
+ "@solidity-parser/parser": "0.19.0",
24
+ "ds-test": "https://github.com/dapphub/ds-test#cd98eff28324bfac652e63a239a60632a761790b",
25
+ "forge-std": "https://github.com/foundry-rs/forge-std#v1.9.1",
26
+ "prettier": "^3.0.3",
27
+ "prettier-plugin-solidity": "^1.4.1",
28
+ "tsup": "^7.2.0",
29
+ "tsx": "^3.13.0",
30
+ "typescript": "^5.2.2",
31
+ "viem": "^2.21.18",
32
+ "@zoralabs/shared-contracts": "^0.0.1",
33
+ "@zoralabs/tsconfig": "^0.0.1",
34
+ "@zoralabs/shared-scripts": "^0.0.0"
35
+ },
36
+ "scripts": {
37
+ "build": "pnpm run wagmi:generate && pnpm run copy-abis && pnpm run prettier:write && tsup",
38
+ "build:sizes": "forge build src/ --sizes",
39
+ "copy-abis": "pnpm exec bundle-abis",
40
+ "coverage": "forge coverage --report lcov --ir-minimum --no-match-coverage '(test/|src/utils/TickMath.sol|script/)'",
41
+ "prettier:check": "prettier --check 'src/**/*.sol' 'test/**/*.sol' 'script/**/*.sol'",
42
+ "prettier:write": "prettier --write 'src/**/*.sol' 'test/**/*.sol' 'script/**/*.sol'",
43
+ "test": "forge test -vv",
44
+ "test-gas": "forge test --gas-report",
45
+ "update-contract-version": "pnpm exec update-contract-version",
46
+ "wagmi:generate": "FOUNDRY_PROFILE=dev forge build && wagmi generate && pnpm exec rename-generated-abi-casing ./package/wagmiGenerated.ts"
47
+ }
48
+ }
package/remappings.txt ADDED
@@ -0,0 +1,4 @@
1
+ ds-test/=node_modules/ds-test/src/
2
+ forge-std/=node_modules/forge-std/src/
3
+ @openzeppelin/=node_modules/@openzeppelin/
4
+ @zoralabs/shared-contracts/=node_modules/@zoralabs/shared-contracts/src/
@@ -0,0 +1,14 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.13;
3
+
4
+ import {Script, console} from "forge-std/Script.sol";
5
+
6
+ contract Deploy is Script {
7
+ function setUp() public {}
8
+
9
+ function run() public {
10
+ vm.startBroadcast();
11
+
12
+ vm.stopBroadcast();
13
+ }
14
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "filter_paths": "(node_modules/,test/)",
3
+ "solc_remaps": [
4
+ "@openzeppelin/=node_modules/@openzeppelin/"
5
+ ]
6
+ }
package/src/Coin.sol ADDED
@@ -0,0 +1,579 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5
+ import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
6
+ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
7
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
8
+ import {Address} from "@openzeppelin/contracts/utils/Address.sol";
9
+ import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
10
+ import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
11
+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
12
+
13
+ import {ContractVersionBase} from "./version/ContractVersionBase.sol";
14
+ import {CoinConstants} from "./utils/CoinConstants.sol";
15
+ import {MultiOwnable} from "./utils/MultiOwnable.sol";
16
+ import {TickMath} from "./utils/TickMath.sol";
17
+ import {ICoin} from "./interfaces/ICoin.sol";
18
+ import {ICoinComments} from "./interfaces/ICoinComments.sol";
19
+ import {IERC7572} from "./interfaces/IERC7572.sol";
20
+ import {INonfungiblePositionManager} from "./interfaces/INonfungiblePositionManager.sol";
21
+ import {IUniswapV3Pool} from "./interfaces/IUniswapV3Pool.sol";
22
+ import {ISwapRouter} from "./interfaces/ISwapRouter.sol";
23
+ import {IProtocolRewards} from "./interfaces/IProtocolRewards.sol";
24
+ import {IWETH} from "./interfaces/IWETH.sol";
25
+
26
+ /*
27
+ $$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\
28
+ $$ __$$\ $$ __$$\ \_$$ _|$$$\ $$ |
29
+ $$ / \__|$$ / $$ | $$ | $$$$\ $$ |
30
+ $$ | $$ | $$ | $$ | $$ $$\$$ |
31
+ $$ | $$ | $$ | $$ | $$ \$$$$ |
32
+ $$ | $$\ $$ | $$ | $$ | $$ |\$$$ |
33
+ \$$$$$$ | $$$$$$ |$$$$$$\ $$ | \$$ |
34
+ \______/ \______/ \______|\__| \__|
35
+ */
36
+ contract Coin is
37
+ ICoin,
38
+ IERC165,
39
+ IERC721Receiver,
40
+ IERC7572,
41
+ CoinConstants,
42
+ ContractVersionBase,
43
+ ERC20PermitUpgradeable,
44
+ MultiOwnable,
45
+ ReentrancyGuardUpgradeable
46
+ {
47
+ using SafeERC20 for IERC20;
48
+
49
+ address public immutable WETH;
50
+ address public immutable nonfungiblePositionManager;
51
+ address public immutable swapRouter;
52
+ address public immutable protocolRewards;
53
+ address public immutable protocolRewardRecipient;
54
+
55
+ address public payoutRecipient;
56
+ address public platformReferrer;
57
+ address public poolAddress;
58
+ address public currency;
59
+ uint256 public lpTokenId;
60
+ string public tokenURI;
61
+
62
+ constructor(
63
+ address _protocolRewardRecipient,
64
+ address _protocolRewards,
65
+ address _weth,
66
+ address _nonfungiblePositionManager,
67
+ address _swapRouter
68
+ ) initializer {
69
+ if (_protocolRewardRecipient == address(0)) {
70
+ revert AddressZero();
71
+ }
72
+ if (_protocolRewards == address(0)) {
73
+ revert AddressZero();
74
+ }
75
+ if (_weth == address(0)) {
76
+ revert AddressZero();
77
+ }
78
+ if (_nonfungiblePositionManager == address(0)) {
79
+ revert AddressZero();
80
+ }
81
+ if (_swapRouter == address(0)) {
82
+ revert AddressZero();
83
+ }
84
+
85
+ protocolRewardRecipient = _protocolRewardRecipient;
86
+ protocolRewards = _protocolRewards;
87
+ WETH = _weth;
88
+ nonfungiblePositionManager = _nonfungiblePositionManager;
89
+ swapRouter = _swapRouter;
90
+ }
91
+
92
+ /// @notice Initializes a new coin
93
+ /// @param payoutRecipient_ The address of the coin creator
94
+ /// @param tokenURI_ The metadata URI
95
+ /// @param name_ The coin name
96
+ /// @param symbol_ The coin symbol
97
+ /// @param platformReferrer_ The address of the platform referrer
98
+ /// @param currency_ The address of the currency
99
+ /// @param tickLower_ The tick lower for the Uniswap V3 pool; ignored for ETH/WETH
100
+ function initialize(
101
+ address payoutRecipient_,
102
+ address[] memory owners_,
103
+ string memory tokenURI_,
104
+ string memory name_,
105
+ string memory symbol_,
106
+ address platformReferrer_,
107
+ address currency_,
108
+ int24 tickLower_
109
+ ) public initializer {
110
+ // Validate the creation parameters
111
+ if (payoutRecipient_ == address(0)) {
112
+ revert AddressZero();
113
+ }
114
+
115
+ // Set base contract state
116
+ __ERC20_init(name_, symbol_);
117
+ __ERC20Permit_init(name_);
118
+ __MultiOwnable_init(owners_);
119
+ __ReentrancyGuard_init();
120
+
121
+ // Set mutable state
122
+ _setPayoutRecipient(payoutRecipient_);
123
+ _setContractURI(tokenURI_);
124
+
125
+ // Set immutable state
126
+ platformReferrer = platformReferrer_ == address(0) ? protocolRewardRecipient : platformReferrer_;
127
+ currency = currency_ == address(0) ? WETH : currency_;
128
+
129
+ // Mint the total supply
130
+ _mint(address(this), MAX_TOTAL_SUPPLY);
131
+
132
+ // Distribute launch rewards
133
+ _transfer(address(this), payoutRecipient, CREATOR_LAUNCH_REWARD);
134
+ _transfer(address(this), platformReferrer, PLATFORM_REFERRER_LAUNCH_REWARD);
135
+ _transfer(address(this), protocolRewardRecipient, PROTOCOL_LAUNCH_REWARD);
136
+
137
+ // Approve the transfer of the remaining supply to the pool
138
+ IERC20(address(this)).safeIncreaseAllowance(address(nonfungiblePositionManager), POOL_LAUNCH_SUPPLY);
139
+
140
+ // Deploy the pool
141
+ _deployPool(tickLower_);
142
+ }
143
+
144
+ /// @notice Executes a buy order
145
+ /// @param recipient The recipient address of the coins
146
+ /// @param orderSize The amount of coins to buy
147
+ /// @param tradeReferrer The address of the trade referrer
148
+ /// @param sqrtPriceLimitX96 The price limit for Uniswap V3 pool swap
149
+ function buy(
150
+ address recipient,
151
+ uint256 orderSize,
152
+ uint256 minAmountOut,
153
+ uint160 sqrtPriceLimitX96,
154
+ address tradeReferrer
155
+ ) public payable nonReentrant returns (uint256) {
156
+ // Ensure the recipient is not the zero address
157
+ if (recipient == address(0)) {
158
+ revert AddressZero();
159
+ }
160
+
161
+ // Calculate the trade reward
162
+ uint256 tradeReward = _calculateReward(orderSize, TOTAL_FEE_BPS);
163
+
164
+ // Calculate the remaining size
165
+ uint256 trueOrderSize = orderSize - tradeReward;
166
+
167
+ // Handle incoming currency
168
+ _handleIncomingCurrency(orderSize, trueOrderSize);
169
+
170
+ // Set up the swap parameters
171
+ ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
172
+ tokenIn: currency,
173
+ tokenOut: address(this),
174
+ fee: LP_FEE,
175
+ recipient: recipient,
176
+ amountIn: trueOrderSize,
177
+ amountOutMinimum: minAmountOut,
178
+ sqrtPriceLimitX96: sqrtPriceLimitX96
179
+ });
180
+
181
+ // Execute the swap
182
+ uint256 amountOut = ISwapRouter(swapRouter).exactInputSingle(params);
183
+
184
+ _handleTradeRewards(tradeReward, tradeReferrer);
185
+
186
+ _handleMarketRewards();
187
+
188
+ emit CoinBuy(msg.sender, recipient, tradeReferrer, amountOut, currency, tradeReward, trueOrderSize, "");
189
+
190
+ return amountOut;
191
+ }
192
+
193
+ /// @notice Executes a sell order
194
+ /// @param recipient The recipient of the currency
195
+ /// @param orderSize The amount of coins to sell
196
+ /// @param minAmountOut The minimum amount of currency to receive
197
+ /// @param sqrtPriceLimitX96 The price limit for the swap
198
+ /// @param tradeReferrer The address of the trade referrer
199
+ function sell(
200
+ address recipient,
201
+ uint256 orderSize,
202
+ uint256 minAmountOut,
203
+ uint160 sqrtPriceLimitX96,
204
+ address tradeReferrer
205
+ ) public nonReentrant returns (uint256) {
206
+ // Ensure the recipient is not the zero address
207
+ if (recipient == address(0)) {
208
+ revert AddressZero();
209
+ }
210
+
211
+ // Transfer the coins from the seller to this contract
212
+ transfer(address(this), orderSize);
213
+
214
+ // Approve the Uniswap V3 swap router
215
+ this.approve(swapRouter, orderSize);
216
+
217
+ // Set the swap parameters
218
+ ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
219
+ tokenIn: address(this),
220
+ tokenOut: currency,
221
+ fee: LP_FEE,
222
+ recipient: address(this),
223
+ amountIn: orderSize,
224
+ amountOutMinimum: minAmountOut,
225
+ sqrtPriceLimitX96: sqrtPriceLimitX96
226
+ });
227
+
228
+ // Execute the swap
229
+ uint256 amountOut = ISwapRouter(swapRouter).exactInputSingle(params);
230
+
231
+ // If currency is WETH, convert to ETH
232
+ if (currency == WETH) {
233
+ IWETH(WETH).withdraw(amountOut);
234
+ }
235
+
236
+ // Calculate the trade reward
237
+ uint256 tradeReward = _calculateReward(amountOut, TOTAL_FEE_BPS);
238
+
239
+ // Calculate the payout after the fee
240
+ uint256 payoutSize = amountOut - tradeReward;
241
+
242
+ _handleSellPayout(payoutSize, recipient);
243
+
244
+ _handleTradeRewards(tradeReward, tradeReferrer);
245
+
246
+ _handleMarketRewards();
247
+
248
+ emit CoinSell(msg.sender, recipient, tradeReferrer, orderSize, currency, tradeReward, payoutSize, "");
249
+
250
+ return amountOut;
251
+ }
252
+
253
+ /// @notice Enables a user to burn their tokens
254
+ /// @param amount The amount of tokens to burn
255
+ function burn(uint256 amount) external {
256
+ _burn(msg.sender, amount);
257
+ }
258
+
259
+ /// @notice Force claim any accrued secondary rewards from the market's liquidity position.
260
+ /// @dev This function is a fallback, secondary rewards will be claimed automatically on each buy and sell.
261
+ /// @param pushEthRewards Whether to push the ETH directly to the recipients.
262
+ function claimSecondaryRewards(bool pushEthRewards) external nonReentrant {
263
+ MarketRewards memory rewards = _handleMarketRewards();
264
+
265
+ if (pushEthRewards && rewards.totalAmountCurrency > 0 && currency == WETH) {
266
+ IProtocolRewards(protocolRewards).withdrawFor(payoutRecipient, rewards.creatorPayoutAmountCurrency);
267
+ IProtocolRewards(protocolRewards).withdrawFor(platformReferrer, rewards.platformReferrerAmountCurrency);
268
+ IProtocolRewards(protocolRewards).withdrawFor(protocolRewardRecipient, rewards.protocolAmountCurrency);
269
+ }
270
+ }
271
+
272
+ /// @notice Set the creator's payout address
273
+ /// @param newPayoutRecipient The new recipient address
274
+ function setPayoutRecipient(address newPayoutRecipient) external onlyOwner {
275
+ _setPayoutRecipient(newPayoutRecipient);
276
+ }
277
+
278
+ /// @notice Set the contract URI
279
+ /// @param newURI The new URI
280
+ function setContractURI(string memory newURI) external onlyOwner {
281
+ _setContractURI(newURI);
282
+ }
283
+
284
+ /// @notice The contract metadata
285
+ function contractURI() external view returns (string memory) {
286
+ return tokenURI;
287
+ }
288
+
289
+ /// @notice ERC165 interface support
290
+ /// @param interfaceId The interface ID to check
291
+ function supportsInterface(bytes4 interfaceId) public pure virtual returns (bool) {
292
+ return
293
+ interfaceId == type(ICoin).interfaceId ||
294
+ interfaceId == type(ICoinComments).interfaceId ||
295
+ interfaceId == type(IERC7572).interfaceId ||
296
+ interfaceId == type(IERC165).interfaceId;
297
+ }
298
+
299
+ /// @notice Receives ETH converted from WETH
300
+ receive() external payable {
301
+ if (msg.sender != WETH) {
302
+ revert OnlyWeth();
303
+ }
304
+ }
305
+
306
+ /// @dev For receiving the Uniswap V3 LP NFT on market graduation.
307
+ function onERC721Received(address, address, uint256, bytes calldata) external view returns (bytes4) {
308
+ if (msg.sender != poolAddress) revert OnlyPool();
309
+
310
+ return this.onERC721Received.selector;
311
+ }
312
+
313
+ /// @dev No-op to allow a swap on the pool to set the correct initial price, if needed
314
+ function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata) external {}
315
+
316
+ /// @dev Overrides ERC20's _update function to
317
+ /// - Prevent transfers to the pool if the market has not graduated.
318
+ /// - Emit the superset `WowTokenTransfer` event with each ERC20 transfer.
319
+ function _update(address from, address to, uint256 value) internal virtual override {
320
+ super._update(from, to, value);
321
+
322
+ emit CoinTransfer(from, to, value, balanceOf(from), balanceOf(to));
323
+ }
324
+
325
+ /// @dev Used to set the payout recipient on coin creation and updates
326
+ /// @param newPayoutRecipient The new recipient address
327
+ function _setPayoutRecipient(address newPayoutRecipient) internal {
328
+ if (newPayoutRecipient == address(0)) {
329
+ revert AddressZero();
330
+ }
331
+
332
+ emit CoinPayoutRecipientUpdated(msg.sender, payoutRecipient, newPayoutRecipient);
333
+
334
+ payoutRecipient = newPayoutRecipient;
335
+ }
336
+
337
+ /// @dev Used to set the contract URI on coin creation and updates
338
+ /// @param newURI The new URI
339
+ function _setContractURI(string memory newURI) internal {
340
+ emit ContractMetadataUpdated(msg.sender, newURI, name());
341
+ emit ContractURIUpdated();
342
+
343
+ tokenURI = newURI;
344
+ }
345
+
346
+ /// @dev Deploy the pool
347
+ function _deployPool(int24 tickLower_) internal {
348
+ // If WETH is the pool's currency, validate the lower tick
349
+ if (currency == WETH && tickLower_ != LP_TICK_LOWER_WETH) {
350
+ revert InvalidWethLowerTick();
351
+ }
352
+
353
+ // Note: This validation happens on the Uniswap pool already; reverting early here for clarity
354
+ // If currency is not WETH: ensure lower tick is less than upper tick and satisfies the 200 tick spacing requirement for 1% Uniswap V3 pools
355
+ if (currency != WETH && (tickLower_ >= LP_TICK_UPPER || tickLower_ % 200 != 0)) {
356
+ revert InvalidCurrencyLowerTick();
357
+ }
358
+
359
+ // Sort the token addresses
360
+ address token0 = address(this) < currency ? address(this) : currency;
361
+ address token1 = address(this) < currency ? currency : address(this);
362
+
363
+ // If the coin is token0
364
+ bool isCoinToken0 = token0 == address(this);
365
+
366
+ // Determine the tick values
367
+ int24 tickLower = isCoinToken0 ? tickLower_ : -LP_TICK_UPPER;
368
+ int24 tickUpper = isCoinToken0 ? LP_TICK_UPPER : -tickLower_;
369
+
370
+ // Calculate the starting price for the pool
371
+ uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(isCoinToken0 ? tickLower : tickUpper);
372
+
373
+ // Determine the initial liquidity amounts
374
+ uint256 amount0 = isCoinToken0 ? POOL_LAUNCH_SUPPLY : 0;
375
+ uint256 amount1 = isCoinToken0 ? 0 : POOL_LAUNCH_SUPPLY;
376
+
377
+ // Create and initialize the pool
378
+ poolAddress = INonfungiblePositionManager(nonfungiblePositionManager).createAndInitializePoolIfNecessary(token0, token1, LP_FEE, sqrtPriceX96);
379
+
380
+ // Construct the LP data
381
+ INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams({
382
+ token0: token0,
383
+ token1: token1,
384
+ fee: LP_FEE,
385
+ tickLower: tickLower,
386
+ tickUpper: tickUpper,
387
+ amount0Desired: amount0,
388
+ amount1Desired: amount1,
389
+ amount0Min: 0,
390
+ amount1Min: 0,
391
+ recipient: address(this),
392
+ deadline: block.timestamp
393
+ });
394
+
395
+ // Mint the LP
396
+ (lpTokenId, , , ) = INonfungiblePositionManager(nonfungiblePositionManager).mint(params);
397
+ }
398
+
399
+ /// @dev Handles incoming currency transfers for buy orders; if WETH is the currency the caller has the option to send native-ETH
400
+ /// @param orderSize The total size of the order in the currency
401
+ /// @param trueOrderSize The actual amount being used for the swap after fees
402
+ function _handleIncomingCurrency(uint256 orderSize, uint256 trueOrderSize) internal {
403
+ if (currency == WETH && msg.value > 0) {
404
+ if (msg.value != orderSize) {
405
+ revert EthAmountMismatch();
406
+ }
407
+
408
+ if (msg.value < MIN_ORDER_SIZE) {
409
+ revert EthAmountTooSmall();
410
+ }
411
+
412
+ IWETH(WETH).deposit{value: trueOrderSize}();
413
+ IWETH(WETH).approve(swapRouter, trueOrderSize);
414
+ } else {
415
+ // Ensure ETH is not sent with a non-ETH pair
416
+ if (msg.value != 0) {
417
+ revert EthTransferInvalid();
418
+ }
419
+
420
+ uint256 beforeBalance = IERC20(currency).balanceOf(address(this));
421
+ IERC20(currency).safeTransferFrom(msg.sender, address(this), orderSize);
422
+ uint256 afterBalance = IERC20(currency).balanceOf(address(this));
423
+
424
+ if ((afterBalance - beforeBalance) != orderSize) {
425
+ revert ERC20TransferAmountMismatch();
426
+ }
427
+
428
+ IERC20(currency).approve(swapRouter, trueOrderSize);
429
+ }
430
+ }
431
+
432
+ /// @dev Handles sending ETH and ERC20 payouts from sell orders to recipients
433
+ /// @param orderPayout The amount of currency to pay out
434
+ /// @param recipient The address to receive the payout
435
+ function _handleSellPayout(uint256 orderPayout, address recipient) internal {
436
+ if (currency == WETH) {
437
+ Address.sendValue(payable(recipient), orderPayout);
438
+ } else {
439
+ IERC20(currency).safeTransfer(recipient, orderPayout);
440
+ }
441
+ }
442
+
443
+ /// @dev Handles calculating and depositing fees to an escrow protocol rewards contract
444
+ function _handleTradeRewards(uint256 totalValue, address _tradeReferrer) internal {
445
+ if (_tradeReferrer == address(0)) {
446
+ _tradeReferrer = protocolRewardRecipient;
447
+ }
448
+
449
+ uint256 tokenCreatorFee = _calculateReward(totalValue, TOKEN_CREATOR_FEE_BPS);
450
+ uint256 platformReferrerFee = _calculateReward(totalValue, PLATFORM_REFERRER_FEE_BPS);
451
+ uint256 tradeReferrerFee = _calculateReward(totalValue, TRADE_REFERRER_FEE_BPS);
452
+ uint256 protocolFee = totalValue - tokenCreatorFee - platformReferrerFee - tradeReferrerFee;
453
+
454
+ if (currency == WETH) {
455
+ address[] memory recipients = new address[](4);
456
+ uint256[] memory amounts = new uint256[](4);
457
+ bytes4[] memory reasons = new bytes4[](4);
458
+
459
+ recipients[0] = payoutRecipient;
460
+ amounts[0] = tokenCreatorFee;
461
+ reasons[0] = bytes4(keccak256("COIN_CREATOR_REWARD"));
462
+
463
+ recipients[1] = platformReferrer;
464
+ amounts[1] = platformReferrerFee;
465
+ reasons[1] = bytes4(keccak256("COIN_PLATFORM_REFERRER_REWARD"));
466
+
467
+ recipients[2] = _tradeReferrer;
468
+ amounts[2] = tradeReferrerFee;
469
+ reasons[2] = bytes4(keccak256("COIN_TRADE_REFERRER_REWARD"));
470
+
471
+ recipients[3] = protocolRewardRecipient;
472
+ amounts[3] = protocolFee;
473
+ reasons[3] = bytes4(keccak256("COIN_PROTOCOL_REWARD"));
474
+
475
+ IProtocolRewards(protocolRewards).depositBatch{value: totalValue}(recipients, amounts, reasons, "");
476
+ }
477
+
478
+ if (currency != WETH) {
479
+ IERC20(currency).safeTransfer(payoutRecipient, tokenCreatorFee);
480
+ IERC20(currency).safeTransfer(platformReferrer, platformReferrerFee);
481
+ IERC20(currency).safeTransfer(_tradeReferrer, tradeReferrerFee);
482
+ IERC20(currency).safeTransfer(protocolRewardRecipient, protocolFee);
483
+ }
484
+
485
+ emit CoinTradeRewards(
486
+ payoutRecipient,
487
+ platformReferrer,
488
+ _tradeReferrer,
489
+ protocolRewardRecipient,
490
+ tokenCreatorFee,
491
+ platformReferrerFee,
492
+ tradeReferrerFee,
493
+ protocolFee,
494
+ currency
495
+ );
496
+ }
497
+
498
+ function _handleMarketRewards() internal returns (MarketRewards memory) {
499
+ INonfungiblePositionManager.CollectParams memory params = INonfungiblePositionManager.CollectParams({
500
+ tokenId: lpTokenId,
501
+ recipient: address(this),
502
+ amount0Max: type(uint128).max,
503
+ amount1Max: type(uint128).max
504
+ });
505
+
506
+ (uint256 totalAmountToken0, uint256 totalAmountToken1) = INonfungiblePositionManager(nonfungiblePositionManager).collect(params);
507
+
508
+ address token0 = currency < address(this) ? currency : address(this);
509
+ address token1 = currency < address(this) ? address(this) : currency;
510
+
511
+ MarketRewards memory rewards;
512
+
513
+ rewards = _transferMarketRewards(token0, totalAmountToken0, rewards);
514
+ rewards = _transferMarketRewards(token1, totalAmountToken1, rewards);
515
+
516
+ emit CoinMarketRewards(payoutRecipient, platformReferrer, protocolRewardRecipient, currency, rewards);
517
+
518
+ return rewards;
519
+ }
520
+
521
+ function _transferMarketRewards(address token, uint256 totalAmount, MarketRewards memory rewards) internal returns (MarketRewards memory) {
522
+ if (totalAmount > 0) {
523
+ uint256 creatorPayout = _calculateReward(totalAmount, CREATOR_MARKET_REWARD_BPS);
524
+ uint256 platformReferrerPayout = _calculateReward(totalAmount, PLATFORM_REFERRER_MARKET_REWARD_BPS);
525
+ uint256 protocolPayout = totalAmount - creatorPayout - platformReferrerPayout;
526
+
527
+ if (token == WETH) {
528
+ IWETH(WETH).withdraw(totalAmount);
529
+
530
+ rewards.totalAmountCurrency = totalAmount;
531
+ rewards.creatorPayoutAmountCurrency = creatorPayout;
532
+ rewards.platformReferrerAmountCurrency = platformReferrerPayout;
533
+ rewards.protocolAmountCurrency = protocolPayout;
534
+
535
+ address[] memory recipients = new address[](3);
536
+ recipients[0] = payoutRecipient;
537
+ recipients[1] = platformReferrer;
538
+ recipients[2] = protocolRewardRecipient;
539
+
540
+ uint256[] memory amounts = new uint256[](3);
541
+ amounts[0] = rewards.creatorPayoutAmountCurrency;
542
+ amounts[1] = rewards.platformReferrerAmountCurrency;
543
+ amounts[2] = rewards.protocolAmountCurrency;
544
+
545
+ bytes4[] memory reasons = new bytes4[](3);
546
+ reasons[0] = bytes4(keccak256("COIN_CREATOR_MARKET_REWARD"));
547
+ reasons[1] = bytes4(keccak256("COIN_PLATFORM_REFERRER_MARKET_REWARD"));
548
+ reasons[2] = bytes4(keccak256("COIN_PROTOCOL_MARKET_REWARD"));
549
+
550
+ IProtocolRewards(protocolRewards).depositBatch{value: totalAmount}(recipients, amounts, reasons, "");
551
+ } else if (token == address(this)) {
552
+ rewards.totalAmountCoin = totalAmount;
553
+ rewards.creatorPayoutAmountCoin = creatorPayout;
554
+ rewards.platformReferrerAmountCoin = platformReferrerPayout;
555
+ rewards.protocolAmountCoin = protocolPayout;
556
+
557
+ _transfer(address(this), payoutRecipient, rewards.creatorPayoutAmountCoin);
558
+ _transfer(address(this), platformReferrer, rewards.platformReferrerAmountCoin);
559
+ _transfer(address(this), protocolRewardRecipient, rewards.protocolAmountCoin);
560
+ } else {
561
+ rewards.totalAmountCurrency = totalAmount;
562
+ rewards.creatorPayoutAmountCurrency = creatorPayout;
563
+ rewards.platformReferrerAmountCurrency = platformReferrerPayout;
564
+ rewards.protocolAmountCurrency = protocolPayout;
565
+
566
+ IERC20(currency).safeTransfer(payoutRecipient, creatorPayout);
567
+ IERC20(currency).safeTransfer(platformReferrer, platformReferrerPayout);
568
+ IERC20(currency).safeTransfer(protocolRewardRecipient, protocolPayout);
569
+ }
570
+ }
571
+
572
+ return rewards;
573
+ }
574
+
575
+ /// @dev Utility for computing amounts in basis points.
576
+ function _calculateReward(uint256 amount, uint256 bps) internal pure returns (uint256) {
577
+ return (amount * bps) / 10_000;
578
+ }
579
+ }