@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.
- package/.turbo/turbo-build.log +76 -0
- package/CHANGELOG.md +14 -0
- package/LICENSE +21 -0
- package/abis/Address.json +29 -0
- package/abis/BaseTest.json +691 -0
- package/abis/Clones.json +7 -0
- package/abis/Coin.json +1693 -0
- package/abis/CoinConstants.json +93 -0
- package/abis/CoinTest.json +998 -0
- package/abis/ContextUpgradeable.json +25 -0
- package/abis/ContractVersionBase.json +15 -0
- package/abis/Deploy.json +29 -0
- package/abis/ECDSA.json +29 -0
- package/abis/EIP712.json +67 -0
- package/abis/EIP712Upgradeable.json +74 -0
- package/abis/ERC1967Proxy.json +67 -0
- package/abis/ERC1967Utils.json +85 -0
- package/abis/ERC20PermitUpgradeable.json +527 -0
- package/abis/ERC20Upgradeable.json +333 -0
- package/abis/FactoryTest.json +845 -0
- package/abis/IBeacon.json +15 -0
- package/abis/ICoin.json +541 -0
- package/abis/ICoinComments.json +53 -0
- package/abis/IERC1155Errors.json +104 -0
- package/abis/IERC165.json +21 -0
- package/abis/IERC1822Proxiable.json +15 -0
- package/abis/IERC20.json +221 -0
- package/abis/IERC20Errors.json +88 -0
- package/abis/IERC20Metadata.json +224 -0
- package/abis/IERC20Permit.json +77 -0
- package/abis/IERC5267.json +51 -0
- package/abis/IERC721.json +287 -0
- package/abis/IERC721Enumerable.json +343 -0
- package/abis/IERC721Errors.json +105 -0
- package/abis/IERC721Metadata.json +332 -0
- package/abis/IERC721Receiver.json +36 -0
- package/abis/IERC721TokenReceiver.json +36 -0
- package/abis/IERC7572.json +21 -0
- package/abis/IMulticall3.json +440 -0
- package/abis/INonfungiblePositionManager.json +366 -0
- package/abis/IProtocolRewards.json +348 -0
- package/abis/ISwapRouter.json +137 -0
- package/abis/IUniswapV3Pool.json +148 -0
- package/abis/IUniswapV3SwapCallback.json +25 -0
- package/abis/IVersionedContract.json +15 -0
- package/abis/IWETH.json +118 -0
- package/abis/IZoraFactory.json +138 -0
- package/abis/Initializable.json +25 -0
- package/abis/Math.json +7 -0
- package/abis/MockERC20.json +322 -0
- package/abis/MockERC721.json +350 -0
- package/abis/MultiOwnable.json +171 -0
- package/abis/MultiOwnableTest.json +796 -0
- package/abis/NoncesUpgradeable.json +60 -0
- package/abis/OwnableUpgradeable.json +99 -0
- package/abis/ProtocolRewards.json +494 -0
- package/abis/Proxy.json +6 -0
- package/abis/ReentrancyGuardUpgradeable.json +30 -0
- package/abis/SafeERC20.json +34 -0
- package/abis/Script.json +15 -0
- package/abis/ShortStrings.json +18 -0
- package/abis/StdAssertions.json +379 -0
- package/abis/StdInvariant.json +180 -0
- package/abis/Strings.json +18 -0
- package/abis/Test.json +570 -0
- package/abis/UUPSUpgradeable.json +130 -0
- package/abis/Vm.json +8627 -0
- package/abis/VmSafe.json +7297 -0
- package/abis/ZoraFactory.json +67 -0
- package/abis/ZoraFactoryImpl.json +422 -0
- package/abis/stdError.json +119 -0
- package/abis/stdStorageSafe.json +52 -0
- package/addresses/1.json +4 -0
- package/addresses/8453.json +9 -0
- package/addresses/84532.json +9 -0
- package/dist/index.cjs +1236 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1208 -0
- package/dist/index.js.map +1 -0
- package/dist/wagmiGenerated.d.ts +1645 -0
- package/dist/wagmiGenerated.d.ts.map +1 -0
- package/foundry.toml +9 -0
- package/package/index.ts +1 -0
- package/package/wagmiGenerated.ts +1211 -0
- package/package.json +48 -0
- package/remappings.txt +4 -0
- package/script/Deploy.s.sol +14 -0
- package/slither.config.json +6 -0
- package/src/Coin.sol +579 -0
- package/src/ZoraFactoryImpl.sol +142 -0
- package/src/interfaces/ICoin.sol +194 -0
- package/src/interfaces/ICoinComments.sol +8 -0
- package/src/interfaces/IERC7572.sol +12 -0
- package/src/interfaces/INonfungiblePositionManager.sol +104 -0
- package/src/interfaces/IProtocolRewards.sol +12 -0
- package/src/interfaces/ISwapRouter.sol +38 -0
- package/src/interfaces/IUniswapV3Pool.sol +43 -0
- package/src/interfaces/IUniswapV3SwapCallback.sol +17 -0
- package/src/interfaces/IWETH.sol +16 -0
- package/src/interfaces/IZoraFactory.sol +56 -0
- package/src/proxy/ZoraFactory.sol +26 -0
- package/src/utils/CoinConstants.sol +67 -0
- package/src/utils/MultiOwnable.sol +126 -0
- package/src/utils/TickMath.sol +210 -0
- package/src/version/ContractVersionBase.sol +14 -0
- package/test/Coin.t.sol +443 -0
- package/test/Factory.t.sol +298 -0
- package/test/MultiOwnable.t.sol +156 -0
- package/test/utils/BaseTest.sol +178 -0
- package/test/utils/ProtocolRewards.sol +1499 -0
- package/tsconfig.build.json +10 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +11 -0
- 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,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
|
+
}
|
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
|
+
}
|