├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── App.css ├── App.js ├── App.test.js ├── artifacts └── EulerGeneralView.json ├── components ├── LiquidityChart │ ├── LiquidityChart.jsx │ └── index.js ├── LiquidityReport │ ├── LiquidityReport.jsx │ └── index.js ├── Main │ ├── Main.jsx │ └── index.jsx └── PriceImpactChart │ ├── PriceImpactChart.jsx │ └── index.js ├── index.css ├── index.js ├── logo.svg ├── lp.json ├── report.json ├── reportWebVitals.js ├── setupTests.js └── utils ├── constants.js ├── index.js ├── liquidityProfile.js ├── tickMath.js ├── trades.js └── utils.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | .env -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Euler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Uniswap Oracle Attack Simulator 2 | The tool calculates the cost to move a token's Uniswap v3 TWAP to x price, given its liquidity profile by binary searching trades on Uniswap QuoterV2 lens. 3 | Check out [this article](https://medium.com/eulerfinance/uniswap-oracle-attack-simulator-42d18adf65af) for details. 4 | 5 | ## Setup 6 | 7 | npm i 8 | 9 | Create `.env` file in the root folder with your provider URL 10 | 11 | ```bash 12 | REACT_APP_ETHEREUM_NETWORK_HTTP= 13 | ``` 14 | 15 | ## Run dev server 16 | 17 | npm start 18 | 19 | ## Run production build 20 | 21 | npm run build -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "euler-oracle-tools", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@apollo/client": "^3.5.7", 7 | "@emotion/react": "^11.7.1", 8 | "@emotion/styled": "^11.6.0", 9 | "@mui/icons-material": "^5.2.5", 10 | "@mui/material": "^5.2.8", 11 | "@testing-library/jest-dom": "^5.16.1", 12 | "@testing-library/react": "^12.1.2", 13 | "@testing-library/user-event": "^13.5.0", 14 | "@uniswap/v3-sdk": "^3.8.1", 15 | "axios": "^0.24.0", 16 | "decimal.js": "^10.3.1", 17 | "ethers": "^5.5.3", 18 | "jsbi": "^4.1.0", 19 | "lodash": "^4.17.21", 20 | "match-sorter": "^6.3.1", 21 | "react": "^17.0.2", 22 | "react-csv": "^2.2.1", 23 | "react-dom": "^17.0.2", 24 | "react-scripts": "5.0.0", 25 | "recharts": "^2.1.8", 26 | "web-vitals": "^2.1.3" 27 | }, 28 | "scripts": { 29 | "start": "env-cmd -f .env react-scripts start", 30 | "build": "react-scripts build", 31 | "test": "react-scripts test", 32 | "eject": "react-scripts eject" 33 | }, 34 | "eslintConfig": { 35 | "extends": [ 36 | "react-app", 37 | "react-app/jest" 38 | ] 39 | }, 40 | "browserslist": { 41 | "production": [ 42 | ">0.2%", 43 | "not dead", 44 | "not op_mini all" 45 | ], 46 | "development": [ 47 | "last 1 chrome version", 48 | "last 1 firefox version", 49 | "last 1 safari version" 50 | ] 51 | }, 52 | "devDependencies": { 53 | "env-cmd": "^10.1.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/euler-oracle-tools/63cc9d8140f8dd72a6bb41f125d290d5737996ab/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Euler Oracle Tools 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/euler-oracle-tools/63cc9d8140f8dd72a6bb41f125d290d5737996ab/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/euler-oracle-tools/63cc9d8140f8dd72a6bb41f125d290d5737996ab/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { Main } from './components/Main'; 2 | 3 | 4 | function App() { 5 | return ( 6 |
7 | ); 8 | } 9 | 10 | export default App; 11 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/artifacts/EulerGeneralView.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "EulerGeneralView", 4 | "sourceName": "contracts/views/EulerGeneralView.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "bytes32", 10 | "name": "moduleGitCommit_", 11 | "type": "bytes32" 12 | } 13 | ], 14 | "stateMutability": "nonpayable", 15 | "type": "constructor" 16 | }, 17 | { 18 | "inputs": [ 19 | { 20 | "internalType": "uint256", 21 | "name": "borrowSPY", 22 | "type": "uint256" 23 | }, 24 | { 25 | "internalType": "uint256", 26 | "name": "totalBorrows", 27 | "type": "uint256" 28 | }, 29 | { 30 | "internalType": "uint256", 31 | "name": "totalBalancesUnderlying", 32 | "type": "uint256" 33 | }, 34 | { 35 | "internalType": "uint32", 36 | "name": "reserveFee", 37 | "type": "uint32" 38 | } 39 | ], 40 | "name": "computeAPYs", 41 | "outputs": [ 42 | { 43 | "internalType": "uint256", 44 | "name": "borrowAPY", 45 | "type": "uint256" 46 | }, 47 | { 48 | "internalType": "uint256", 49 | "name": "supplyAPY", 50 | "type": "uint256" 51 | } 52 | ], 53 | "stateMutability": "pure", 54 | "type": "function" 55 | }, 56 | { 57 | "inputs": [ 58 | { 59 | "components": [ 60 | { 61 | "internalType": "address", 62 | "name": "eulerContract", 63 | "type": "address" 64 | }, 65 | { 66 | "internalType": "address", 67 | "name": "account", 68 | "type": "address" 69 | }, 70 | { 71 | "internalType": "address[]", 72 | "name": "markets", 73 | "type": "address[]" 74 | } 75 | ], 76 | "internalType": "struct EulerGeneralView.Query", 77 | "name": "q", 78 | "type": "tuple" 79 | } 80 | ], 81 | "name": "doQuery", 82 | "outputs": [ 83 | { 84 | "components": [ 85 | { 86 | "internalType": "uint256", 87 | "name": "timestamp", 88 | "type": "uint256" 89 | }, 90 | { 91 | "internalType": "uint256", 92 | "name": "blockNumber", 93 | "type": "uint256" 94 | }, 95 | { 96 | "components": [ 97 | { 98 | "internalType": "address", 99 | "name": "underlying", 100 | "type": "address" 101 | }, 102 | { 103 | "internalType": "string", 104 | "name": "name", 105 | "type": "string" 106 | }, 107 | { 108 | "internalType": "string", 109 | "name": "symbol", 110 | "type": "string" 111 | }, 112 | { 113 | "internalType": "uint8", 114 | "name": "decimals", 115 | "type": "uint8" 116 | }, 117 | { 118 | "internalType": "address", 119 | "name": "eTokenAddr", 120 | "type": "address" 121 | }, 122 | { 123 | "internalType": "address", 124 | "name": "dTokenAddr", 125 | "type": "address" 126 | }, 127 | { 128 | "internalType": "address", 129 | "name": "pTokenAddr", 130 | "type": "address" 131 | }, 132 | { 133 | "components": [ 134 | { 135 | "internalType": "address", 136 | "name": "eTokenAddress", 137 | "type": "address" 138 | }, 139 | { 140 | "internalType": "bool", 141 | "name": "borrowIsolated", 142 | "type": "bool" 143 | }, 144 | { 145 | "internalType": "uint32", 146 | "name": "collateralFactor", 147 | "type": "uint32" 148 | }, 149 | { 150 | "internalType": "uint32", 151 | "name": "borrowFactor", 152 | "type": "uint32" 153 | }, 154 | { 155 | "internalType": "uint24", 156 | "name": "twapWindow", 157 | "type": "uint24" 158 | } 159 | ], 160 | "internalType": "struct Storage.AssetConfig", 161 | "name": "config", 162 | "type": "tuple" 163 | }, 164 | { 165 | "internalType": "uint256", 166 | "name": "poolSize", 167 | "type": "uint256" 168 | }, 169 | { 170 | "internalType": "uint256", 171 | "name": "totalBalances", 172 | "type": "uint256" 173 | }, 174 | { 175 | "internalType": "uint256", 176 | "name": "totalBorrows", 177 | "type": "uint256" 178 | }, 179 | { 180 | "internalType": "uint256", 181 | "name": "reserveBalance", 182 | "type": "uint256" 183 | }, 184 | { 185 | "internalType": "uint32", 186 | "name": "reserveFee", 187 | "type": "uint32" 188 | }, 189 | { 190 | "internalType": "uint256", 191 | "name": "borrowAPY", 192 | "type": "uint256" 193 | }, 194 | { 195 | "internalType": "uint256", 196 | "name": "supplyAPY", 197 | "type": "uint256" 198 | }, 199 | { 200 | "internalType": "uint256", 201 | "name": "twap", 202 | "type": "uint256" 203 | }, 204 | { 205 | "internalType": "uint256", 206 | "name": "twapPeriod", 207 | "type": "uint256" 208 | }, 209 | { 210 | "internalType": "uint256", 211 | "name": "currPrice", 212 | "type": "uint256" 213 | }, 214 | { 215 | "internalType": "uint16", 216 | "name": "pricingType", 217 | "type": "uint16" 218 | }, 219 | { 220 | "internalType": "uint32", 221 | "name": "pricingParameters", 222 | "type": "uint32" 223 | }, 224 | { 225 | "internalType": "address", 226 | "name": "pricingForwarded", 227 | "type": "address" 228 | }, 229 | { 230 | "internalType": "uint256", 231 | "name": "underlyingBalance", 232 | "type": "uint256" 233 | }, 234 | { 235 | "internalType": "uint256", 236 | "name": "eulerAllowance", 237 | "type": "uint256" 238 | }, 239 | { 240 | "internalType": "uint256", 241 | "name": "eTokenBalance", 242 | "type": "uint256" 243 | }, 244 | { 245 | "internalType": "uint256", 246 | "name": "eTokenBalanceUnderlying", 247 | "type": "uint256" 248 | }, 249 | { 250 | "internalType": "uint256", 251 | "name": "dTokenBalance", 252 | "type": "uint256" 253 | }, 254 | { 255 | "components": [ 256 | { 257 | "internalType": "uint256", 258 | "name": "collateralValue", 259 | "type": "uint256" 260 | }, 261 | { 262 | "internalType": "uint256", 263 | "name": "liabilityValue", 264 | "type": "uint256" 265 | }, 266 | { 267 | "internalType": "uint256", 268 | "name": "numBorrows", 269 | "type": "uint256" 270 | }, 271 | { 272 | "internalType": "bool", 273 | "name": "borrowIsolated", 274 | "type": "bool" 275 | } 276 | ], 277 | "internalType": "struct IRiskManager.LiquidityStatus", 278 | "name": "liquidityStatus", 279 | "type": "tuple" 280 | } 281 | ], 282 | "internalType": "struct EulerGeneralView.ResponseMarket[]", 283 | "name": "markets", 284 | "type": "tuple[]" 285 | }, 286 | { 287 | "internalType": "address[]", 288 | "name": "enteredMarkets", 289 | "type": "address[]" 290 | }, 291 | { 292 | "internalType": "uint256", 293 | "name": "averageLiquidity", 294 | "type": "uint256" 295 | }, 296 | { 297 | "internalType": "address", 298 | "name": "averageLiquidityDelegate", 299 | "type": "address" 300 | } 301 | ], 302 | "internalType": "struct EulerGeneralView.Response", 303 | "name": "r", 304 | "type": "tuple" 305 | } 306 | ], 307 | "stateMutability": "nonpayable", 308 | "type": "function" 309 | }, 310 | { 311 | "inputs": [ 312 | { 313 | "internalType": "address", 314 | "name": "eulerContract", 315 | "type": "address" 316 | }, 317 | { 318 | "internalType": "address[]", 319 | "name": "addrs", 320 | "type": "address[]" 321 | } 322 | ], 323 | "name": "doQueryAccountLiquidity", 324 | "outputs": [ 325 | { 326 | "components": [ 327 | { 328 | "components": [ 329 | { 330 | "internalType": "address", 331 | "name": "underlying", 332 | "type": "address" 333 | }, 334 | { 335 | "components": [ 336 | { 337 | "internalType": "uint256", 338 | "name": "collateralValue", 339 | "type": "uint256" 340 | }, 341 | { 342 | "internalType": "uint256", 343 | "name": "liabilityValue", 344 | "type": "uint256" 345 | }, 346 | { 347 | "internalType": "uint256", 348 | "name": "numBorrows", 349 | "type": "uint256" 350 | }, 351 | { 352 | "internalType": "bool", 353 | "name": "borrowIsolated", 354 | "type": "bool" 355 | } 356 | ], 357 | "internalType": "struct IRiskManager.LiquidityStatus", 358 | "name": "status", 359 | "type": "tuple" 360 | } 361 | ], 362 | "internalType": "struct IRiskManager.AssetLiquidity[]", 363 | "name": "markets", 364 | "type": "tuple[]" 365 | } 366 | ], 367 | "internalType": "struct EulerGeneralView.ResponseAccountLiquidity[]", 368 | "name": "r", 369 | "type": "tuple[]" 370 | } 371 | ], 372 | "stateMutability": "nonpayable", 373 | "type": "function" 374 | }, 375 | { 376 | "inputs": [ 377 | { 378 | "components": [ 379 | { 380 | "internalType": "address", 381 | "name": "eulerContract", 382 | "type": "address" 383 | }, 384 | { 385 | "internalType": "address", 386 | "name": "account", 387 | "type": "address" 388 | }, 389 | { 390 | "internalType": "address[]", 391 | "name": "markets", 392 | "type": "address[]" 393 | } 394 | ], 395 | "internalType": "struct EulerGeneralView.Query[]", 396 | "name": "qs", 397 | "type": "tuple[]" 398 | } 399 | ], 400 | "name": "doQueryBatch", 401 | "outputs": [ 402 | { 403 | "components": [ 404 | { 405 | "internalType": "uint256", 406 | "name": "timestamp", 407 | "type": "uint256" 408 | }, 409 | { 410 | "internalType": "uint256", 411 | "name": "blockNumber", 412 | "type": "uint256" 413 | }, 414 | { 415 | "components": [ 416 | { 417 | "internalType": "address", 418 | "name": "underlying", 419 | "type": "address" 420 | }, 421 | { 422 | "internalType": "string", 423 | "name": "name", 424 | "type": "string" 425 | }, 426 | { 427 | "internalType": "string", 428 | "name": "symbol", 429 | "type": "string" 430 | }, 431 | { 432 | "internalType": "uint8", 433 | "name": "decimals", 434 | "type": "uint8" 435 | }, 436 | { 437 | "internalType": "address", 438 | "name": "eTokenAddr", 439 | "type": "address" 440 | }, 441 | { 442 | "internalType": "address", 443 | "name": "dTokenAddr", 444 | "type": "address" 445 | }, 446 | { 447 | "internalType": "address", 448 | "name": "pTokenAddr", 449 | "type": "address" 450 | }, 451 | { 452 | "components": [ 453 | { 454 | "internalType": "address", 455 | "name": "eTokenAddress", 456 | "type": "address" 457 | }, 458 | { 459 | "internalType": "bool", 460 | "name": "borrowIsolated", 461 | "type": "bool" 462 | }, 463 | { 464 | "internalType": "uint32", 465 | "name": "collateralFactor", 466 | "type": "uint32" 467 | }, 468 | { 469 | "internalType": "uint32", 470 | "name": "borrowFactor", 471 | "type": "uint32" 472 | }, 473 | { 474 | "internalType": "uint24", 475 | "name": "twapWindow", 476 | "type": "uint24" 477 | } 478 | ], 479 | "internalType": "struct Storage.AssetConfig", 480 | "name": "config", 481 | "type": "tuple" 482 | }, 483 | { 484 | "internalType": "uint256", 485 | "name": "poolSize", 486 | "type": "uint256" 487 | }, 488 | { 489 | "internalType": "uint256", 490 | "name": "totalBalances", 491 | "type": "uint256" 492 | }, 493 | { 494 | "internalType": "uint256", 495 | "name": "totalBorrows", 496 | "type": "uint256" 497 | }, 498 | { 499 | "internalType": "uint256", 500 | "name": "reserveBalance", 501 | "type": "uint256" 502 | }, 503 | { 504 | "internalType": "uint32", 505 | "name": "reserveFee", 506 | "type": "uint32" 507 | }, 508 | { 509 | "internalType": "uint256", 510 | "name": "borrowAPY", 511 | "type": "uint256" 512 | }, 513 | { 514 | "internalType": "uint256", 515 | "name": "supplyAPY", 516 | "type": "uint256" 517 | }, 518 | { 519 | "internalType": "uint256", 520 | "name": "twap", 521 | "type": "uint256" 522 | }, 523 | { 524 | "internalType": "uint256", 525 | "name": "twapPeriod", 526 | "type": "uint256" 527 | }, 528 | { 529 | "internalType": "uint256", 530 | "name": "currPrice", 531 | "type": "uint256" 532 | }, 533 | { 534 | "internalType": "uint16", 535 | "name": "pricingType", 536 | "type": "uint16" 537 | }, 538 | { 539 | "internalType": "uint32", 540 | "name": "pricingParameters", 541 | "type": "uint32" 542 | }, 543 | { 544 | "internalType": "address", 545 | "name": "pricingForwarded", 546 | "type": "address" 547 | }, 548 | { 549 | "internalType": "uint256", 550 | "name": "underlyingBalance", 551 | "type": "uint256" 552 | }, 553 | { 554 | "internalType": "uint256", 555 | "name": "eulerAllowance", 556 | "type": "uint256" 557 | }, 558 | { 559 | "internalType": "uint256", 560 | "name": "eTokenBalance", 561 | "type": "uint256" 562 | }, 563 | { 564 | "internalType": "uint256", 565 | "name": "eTokenBalanceUnderlying", 566 | "type": "uint256" 567 | }, 568 | { 569 | "internalType": "uint256", 570 | "name": "dTokenBalance", 571 | "type": "uint256" 572 | }, 573 | { 574 | "components": [ 575 | { 576 | "internalType": "uint256", 577 | "name": "collateralValue", 578 | "type": "uint256" 579 | }, 580 | { 581 | "internalType": "uint256", 582 | "name": "liabilityValue", 583 | "type": "uint256" 584 | }, 585 | { 586 | "internalType": "uint256", 587 | "name": "numBorrows", 588 | "type": "uint256" 589 | }, 590 | { 591 | "internalType": "bool", 592 | "name": "borrowIsolated", 593 | "type": "bool" 594 | } 595 | ], 596 | "internalType": "struct IRiskManager.LiquidityStatus", 597 | "name": "liquidityStatus", 598 | "type": "tuple" 599 | } 600 | ], 601 | "internalType": "struct EulerGeneralView.ResponseMarket[]", 602 | "name": "markets", 603 | "type": "tuple[]" 604 | }, 605 | { 606 | "internalType": "address[]", 607 | "name": "enteredMarkets", 608 | "type": "address[]" 609 | }, 610 | { 611 | "internalType": "uint256", 612 | "name": "averageLiquidity", 613 | "type": "uint256" 614 | }, 615 | { 616 | "internalType": "address", 617 | "name": "averageLiquidityDelegate", 618 | "type": "address" 619 | } 620 | ], 621 | "internalType": "struct EulerGeneralView.Response[]", 622 | "name": "r", 623 | "type": "tuple[]" 624 | } 625 | ], 626 | "stateMutability": "nonpayable", 627 | "type": "function" 628 | }, 629 | { 630 | "inputs": [ 631 | { 632 | "components": [ 633 | { 634 | "internalType": "address", 635 | "name": "eulerContract", 636 | "type": "address" 637 | }, 638 | { 639 | "internalType": "address", 640 | "name": "underlying", 641 | "type": "address" 642 | } 643 | ], 644 | "internalType": "struct EulerGeneralView.QueryIRM", 645 | "name": "q", 646 | "type": "tuple" 647 | } 648 | ], 649 | "name": "doQueryIRM", 650 | "outputs": [ 651 | { 652 | "components": [ 653 | { 654 | "internalType": "uint256", 655 | "name": "kink", 656 | "type": "uint256" 657 | }, 658 | { 659 | "internalType": "uint256", 660 | "name": "baseAPY", 661 | "type": "uint256" 662 | }, 663 | { 664 | "internalType": "uint256", 665 | "name": "kinkAPY", 666 | "type": "uint256" 667 | }, 668 | { 669 | "internalType": "uint256", 670 | "name": "maxAPY", 671 | "type": "uint256" 672 | }, 673 | { 674 | "internalType": "uint256", 675 | "name": "baseSupplyAPY", 676 | "type": "uint256" 677 | }, 678 | { 679 | "internalType": "uint256", 680 | "name": "kinkSupplyAPY", 681 | "type": "uint256" 682 | }, 683 | { 684 | "internalType": "uint256", 685 | "name": "maxSupplyAPY", 686 | "type": "uint256" 687 | } 688 | ], 689 | "internalType": "struct EulerGeneralView.ResponseIRM", 690 | "name": "r", 691 | "type": "tuple" 692 | } 693 | ], 694 | "stateMutability": "view", 695 | "type": "function" 696 | }, 697 | { 698 | "inputs": [], 699 | "name": "moduleGitCommit", 700 | "outputs": [ 701 | { 702 | "internalType": "bytes32", 703 | "name": "", 704 | "type": "bytes32" 705 | } 706 | ], 707 | "stateMutability": "view", 708 | "type": "function" 709 | } 710 | ], 711 | "bytecode": "0x60a06040523480156200001157600080fd5b5060405162002f2d38038062002f2d83398101604081905262000034916200003d565b60805262000057565b6000602082840312156200005057600080fd5b5051919050565b608051612eba62000073600039600061012e0152612eba6000f3fe608060405234801561001057600080fd5b50600436106100725760003560e01c806369a92ea31161005057806369a92ea314610129578063854c87271461015e5780638f863b2e1461017e57600080fd5b8063340d059a146100775780633d77bf7f146100a05780634c0cf6f714610109575b600080fd5b61008a610085366004612149565b6101a6565b604051610097919061262e565b60405180910390f35b6100b36100ae3660046126ae565b6102a7565b6040516100979190600060e082019050825182526020830151602083015260408301516040830152606083015160608301526080830151608083015260a083015160a083015260c083015160c083015292915050565b61011c61011736600461270d565b6107b3565b604051610097919061275d565b6101507f000000000000000000000000000000000000000000000000000000000000000081565b604051908152602001610097565b61017161016c36600461285e565b6109bf565b604051610097919061289b565b61019161018c3660046128c7565b611014565b60408051928352602083019190915201610097565b6060815167ffffffffffffffff8111156101c2576101c2611f32565b60405190808252806020026020018201604052801561024257816020015b61022f6040518060c001604052806000815260200160008152602001606081526020016060815260200160008152602001600073ffffffffffffffffffffffffffffffffffffffff1681525090565b8152602001906001900390816101e05790505b50905060005b82518110156102a15761027383828151811061026657610266612908565b60200260200101516109bf565b82828151811061028557610285612908565b60200260200101819052508061029a90612966565b9050610248565b50919050565b6102e76040518060e00160405280600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b81516040517f734c938f0000000000000000000000000000000000000000000000000000000081526002600482015260009073ffffffffffffffffffffffffffffffffffffffff83169063734c938f90602401602060405180830381865afa158015610357573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061037b919061299f565b60208501516040517fb409dd9b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529192506000919083169063b409dd9b90602401602060405180830381865afa1580156103f3573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061041791906129bc565b6040517fcab65f010000000000000000000000000000000000000000000000000000000081526004810182905290915060009073ffffffffffffffffffffffffffffffffffffffff85169063cab65f0190602401602060405180830381865afa158015610488573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104ac919061299f565b9050600081905060008173ffffffffffffffffffffffffffffffffffffffff1663fd2da3396040518163ffffffff1660e01b8152600401602060405180830381865afa158015610500573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061052491906129bc565b80885260208901516040517fb74b1ed500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529192506000919087169063b74b1ed590602401602060405180830381865afa15801561059f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105c391906129d5565b905060008373ffffffffffffffffffffffffffffffffffffffff16631f68f20a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610612573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061063691906129bc565b905060008473ffffffffffffffffffffffffffffffffffffffff1663a62b75a86040518163ffffffff1660e01b8152600401602060405180830381865afa158015610685573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106a991906129bc565b6106b390856129f2565b6106bd9083612a2f565b905060008573ffffffffffffffffffffffffffffffffffffffff1663d0134cb76040518163ffffffff1660e01b8152600401602060405180830381865afa15801561070c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061073091906129bc565b61073e8663ffffffff612a47565b61074891906129f2565b6107529083612a2f565b905061076583600063ffffffff87611014565b60808d015260208c015261077f828663ffffffff87611014565b60a08d015260408c01526107998163ffffffff8087611014565b60c08d015260608c015250989a9950505050505050505050565b6040517f734c938f00000000000000000000000000000000000000000000000000000000815260056004820152606090839060009073ffffffffffffffffffffffffffffffffffffffff83169063734c938f90602401602060405180830381865afa158015610826573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061084a919061299f565b9050835167ffffffffffffffff81111561086657610866611f32565b6040519080825280602002602001820160405280156108a657816020015b6040805160208101909152606081528152602001906001900390816108845790505b50925060005b84518110156109b6578173ffffffffffffffffffffffffffffffffffffffff1663dec82caf8683815181106108e3576108e3612908565b60200260200101516040518263ffffffff1660e01b8152600401610923919073ffffffffffffffffffffffffffffffffffffffff91909116815260200190565b6000604051808303816000875af1158015610942573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526109889190810190612a73565b84828151811061099a5761099a612908565b6020908102919091010151526109af81612966565b90506108ac565b50505092915050565b610a0e6040518060c001604052806000815260200160008152602001606081526020016060815260200160008152602001600073ffffffffffffffffffffffffffffffffffffffff1681525090565b42815243602082015281516040517f734c938f0000000000000000000000000000000000000000000000000000000081526002600482015260009073ffffffffffffffffffffffffffffffffffffffff83169063734c938f90602401602060405180830381865afa158015610a87573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610aab919061299f565b6040517f734c938f0000000000000000000000000000000000000000000000000000000081526005600482015290915060009073ffffffffffffffffffffffffffffffffffffffff84169063734c938f90602401602060405180830381865afa158015610b1c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b40919061299f565b602086015190915060609073ffffffffffffffffffffffffffffffffffffffff1615610c235760208601516040517fdec82caf00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529083169063dec82caf906024016000604051808303816000875af1158015610bda573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610c209190810190612a73565b90505b8560400151518151610c359190612a2f565b67ffffffffffffffff811115610c4d57610c4d611f32565b604051908082528060200260200182016040528015610c8657816020015b610c73611df4565b815260200190600190039081610c6b5790505b50604086015260005b8151811015610d3657600086604001518281518110610cb057610cb0612908565b60200260200101519050828281518110610ccc57610ccc612908565b60209081029190910101515173ffffffffffffffffffffffffffffffffffffffff1681528251839083908110610d0457610d04612908565b602002602001015160200151816103400181905250610d25888287876110d6565b50610d2f81612966565b9050610c8f565b5080515b8660400151518251610d4c9190612a2f565b811015610dde576000825182610d629190612a47565b9050600087604001518381518110610d7c57610d7c612908565b6020026020010151905088604001518281518110610d9c57610d9c612908565b602090810291909101015173ffffffffffffffffffffffffffffffffffffffff168152610dcb898288886110d6565b505080610dd790612966565b9050610d3a565b50602086015173ffffffffffffffffffffffffffffffffffffffff161561100b5760208601516040517f8ccb720b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff918216600482015290841690638ccb720b90602401600060405180830381865afa158015610e71573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610eb79190810190612b97565b606086015260208601516040517fe94f487f00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529083169063e94f487f906024016020604051808303816000875af1158015610f30573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f5491906129bc565b608086015260208601516040517fdf6b2aab00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529083169063df6b2aab90602401602060405180830381865afa158015610fcb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fef919061299f565b73ffffffffffffffffffffffffffffffffffffffff1660a08601525b50505050919050565b6000806b033b2e3c9fd0803ce80000006110486110318883612a2f565b6301e185586b033b2e3c9fd0803ce8000000611d36565b6110529190612a47565b915060008415611076578461106787896129f2565b6110719190612c31565b611079565b60005b905063ee6b280061109063ffffffff861682612a47565b61109a90836129f2565b6110a49190612c31565b90506b033b2e3c9fd0803ce80000006110c06110318383612a2f565b6110ca9190612a47565b91505094509492505050565b826000015173ffffffffffffffffffffffffffffffffffffffff166306fdde036040518163ffffffff1660e01b8152600401600060405180830381865afa158015611125573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261116b9190810190612c6c565b8360200181905250826000015173ffffffffffffffffffffffffffffffffffffffff166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa1580156111c2573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526112089190810190612c6c565b8360400181905250826000015173ffffffffffffffffffffffffffffffffffffffff1663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801561125f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112839190612d1e565b60ff16606084015282516040517f8948874900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff918216600482015290831690638948874990602401602060405180830381865afa1580156112fa573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061131e919061299f565b73ffffffffffffffffffffffffffffffffffffffff166080840181905261134457611d30565b60808301516040517f6b5e360600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff918216600482015290831690636b5e360690602401602060405180830381865afa1580156113b6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113da919061299f565b73ffffffffffffffffffffffffffffffffffffffff90811660a085015283516040517ffd7948d400000000000000000000000000000000000000000000000000000000815290821660048201529083169063fd7948d490602401602060405180830381865afa158015611451573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611475919061299f565b73ffffffffffffffffffffffffffffffffffffffff90811660c085015283516040517f4e9cfac20000000000000000000000000000000000000000000000000000000081529082166004820152600091841690634e9cfac29060240160a060405180830381865afa1580156114ee573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115129190612d41565b60e085015250825184516040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529116906370a0823190602401602060405180830381865afa158015611588573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115ac91906129bc565b83610100018181525050826080015173ffffffffffffffffffffffffffffffffffffffff1663fea61faa6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611605573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061162991906129bc565b836101200181815250508260a0015173ffffffffffffffffffffffffffffffffffffffff166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611682573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116a691906129bc565b83610140018181525050826080015173ffffffffffffffffffffffffffffffffffffffff1663e73277d06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156116ff573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061172391906129bc565b61016084015282516040517fb74b1ed500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529083169063b74b1ed590602401602060405180830381865afa158015611798573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117bc91906129d5565b63ffffffff1661018084015282516040517f7c2c69c000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9182166004820152600091841690637c2c69c090602401602060405180830381865afa158015611839573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061185d9190612ddf565b600b0b905061187d81856101400151866101200151876101800151611014565b6101c08601526101a08501525082516040517f9693fa6b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff918216600482015290821690639693fa6b906024016060604051808303816000875af11580156118fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061191f9190612e02565b6102208601526102008501526101e084015282516040517f546422d600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529083169063546422d690602401606060405180830381865afa1580156119a0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119c49190612e30565b73ffffffffffffffffffffffffffffffffffffffff90811661028087015263ffffffff90911661026086015261ffff909116610240850152602085015116611a0b57611d30565b825160208501516040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529116906370a0823190602401602060405180830381865afa158015611a7e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611aa291906129bc565b6102a0840152608083015160208501516040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529116906370a0823190602401602060405180830381865afa158015611b1e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b4291906129bc565b6102e0840152608083015160208501516040517f3af9e66900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9182166004820152911690633af9e66990602401602060405180830381865afa158015611bbe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611be291906129bc565b61030084015260a083015160208501516040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529116906370a0823190602401602060405180830381865afa158015611c5e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c8291906129bc565b6103208401528251602085015185516040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9283166004820152908216602482015291169063dd62ed3e90604401602060405180830381865afa158015611d05573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d2991906129bc565b6102c08401525b50505050565b6000838015611dd657600184168015611d5157859250611d55565b8392505b50600283046002850494505b8415611dd0578586028687820414611d7857600080fd5b81810181811015611d8857600080fd5b8590049650506001851615611dc5578583028387820414158715151615611dae57600080fd5b81810181811015611dbe57600080fd5b8590049350505b600285049450611d61565b50611dec565b838015611de65760009250611dea565b8392505b505b509392505050565b6040805161036081018252600080825260606020808401829052838501829052818401839052608080850184905260a080860185905260c0860185905286519081018752848152918201849052948101839052908101829052928301529060e0820190815260200160008152602001600081526020016000815260200160008152602001600063ffffffff1681526020016000815260200160008152602001600081526020016000815260200160008152602001600061ffff168152602001600063ffffffff168152602001600073ffffffffffffffffffffffffffffffffffffffff1681526020016000815260200160008152602001600081526020016000815260200160008152602001611f2d60405180608001604052806000815260200160008152602001600081526020016000151581525090565b905290565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff81118282101715611f8457611f84611f32565b60405290565b6040516080810167ffffffffffffffff81118282101715611f8457611f84611f32565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715611ff457611ff4611f32565b604052919050565b600067ffffffffffffffff82111561201657612016611f32565b5060051b60200190565b73ffffffffffffffffffffffffffffffffffffffff8116811461204257600080fd5b50565b600082601f83011261205657600080fd5b8135602061206b61206683611ffc565b611fad565b82815260059290921b8401810191818101908684111561208a57600080fd5b8286015b848110156120ae5780356120a181612020565b835291830191830161208e565b509695505050505050565b6000606082840312156120cb57600080fd5b6040516060810167ffffffffffffffff82821081831117156120ef576120ef611f32565b816040528293508435915061210382612020565b90825260208401359061211582612020565b816020840152604085013591508082111561212f57600080fd5b5061213c85828601612045565b6040830152505092915050565b6000602080838503121561215c57600080fd5b823567ffffffffffffffff8082111561217457600080fd5b818501915085601f83011261218857600080fd5b813561219661206682611ffc565b81815260059190911b830184019084810190888311156121b557600080fd5b8585015b838110156121ed578035858111156121d15760008081fd5b6121df8b89838a01016120b9565b8452509186019186016121b9565b5098975050505050505050565b60005b838110156122155781810151838201526020016121fd565b83811115611d305750506000910152565b6000815180845261223e8160208601602086016121fa565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600081518084526020808501945080840160005b838110156122b657815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101612284565b509495945050505050565b600060c08084018351855260208085015181870152604080860151848289015283815180865260e09550858a019150858160051b8b0101858401935060005b828110156125da578b82037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff200184528451805173ffffffffffffffffffffffffffffffffffffffff16835261044088820151818a86015261236382860182612226565b915050878201518482038986015261237b8282612226565b9150506060808301516123928287018260ff169052565b505060808281015173ffffffffffffffffffffffffffffffffffffffff811686830152505060a08281015173ffffffffffffffffffffffffffffffffffffffff8116868301525050818b015173ffffffffffffffffffffffffffffffffffffffff8116858d0152508982015161245e8b86018273ffffffffffffffffffffffffffffffffffffffff8151168252602081015115156020830152604081015163ffffffff8082166040850152806060840151166060850152505062ffffff60808201511660808301525050565b50610100820151610180818187015261012084015191506101a0828188015261014085015192506101c0838189015261016086015193506101e084818a015283870151945061020093506124b9848a018663ffffffff169052565b918601516102208981019190915290860151610240808a019190915291860151610260808a019190915292860151610280808a0191909152908601516102a0808a0191909152918601519350916102c090612519828a018661ffff169052565b86015193506102e06125328982018663ffffffff169052565b9286015193506103009261255d8985018673ffffffffffffffffffffffffffffffffffffffff169052565b918601516103208981019190915290860151610340808a01919091529186015161036089015291850151610380880152908401516103a08701529092015180516103c086015260208101516103e0860152604081015161040086015260600151151561042090940193909352509386019392860192600101612300565b5060608a015197508a810360608c01526125f48189612270565b9750505050505050506080830151608085015260a0830151611dec60a086018273ffffffffffffffffffffffffffffffffffffffff169052565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b828110156126a1577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc088860301845261268f8583516122c1565b94509285019290850190600101612655565b5092979650505050505050565b6000604082840312156126c057600080fd5b6040516040810181811067ffffffffffffffff821117156126e3576126e3611f32565b60405282356126f181612020565b8152602083013561270181612020565b60208201529392505050565b6000806040838503121561272057600080fd5b823561272b81612020565b9150602083013567ffffffffffffffff81111561274757600080fd5b61275385828601612045565b9150509250929050565b60006020808301818452808551808352604092508286019150828160051b8701018488016000805b8481101561284f578984037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00186528251518885528051898601819052908901908390898701905b8083101561283a578351805173ffffffffffffffffffffffffffffffffffffffff1683528c01516128238d8401828051825260208101516020830152604081015160408301526060810151151560608301525050565b5060a0820191508b840193506001830192506127cd565b50978a01979550505091870191600101612785565b50919998505050505050505050565b60006020828403121561287057600080fd5b813567ffffffffffffffff81111561288757600080fd5b612893848285016120b9565b949350505050565b6020815260006128ae60208301846122c1565b9392505050565b63ffffffff8116811461204257600080fd5b600080600080608085870312156128dd57600080fd5b84359350602085013592506040850135915060608501356128fd816128b5565b939692955090935050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82141561299857612998612937565b5060010190565b6000602082840312156129b157600080fd5b81516128ae81612020565b6000602082840312156129ce57600080fd5b5051919050565b6000602082840312156129e757600080fd5b81516128ae816128b5565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615612a2a57612a2a612937565b500290565b60008219821115612a4257612a42612937565b500190565b600082821015612a5957612a59612937565b500390565b80518015158114612a6e57600080fd5b919050565b60006020808385031215612a8657600080fd5b825167ffffffffffffffff811115612a9d57600080fd5b8301601f81018513612aae57600080fd5b8051612abc61206682611ffc565b81815260a09182028301840191848201919088841115612adb57600080fd5b938501935b83851015612b8b5784890381811215612af95760008081fd5b612b01611f61565b8651612b0c81612020565b815260807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08301811315612b405760008081fd5b612b48611f8a565b92508888015183526040808901518a8501526060808a015182860152612b6f838b01612a5e565b9085015250508088019190915283529384019391850191612ae0565b50979650505050505050565b60006020808385031215612baa57600080fd5b825167ffffffffffffffff811115612bc157600080fd5b8301601f81018513612bd257600080fd5b8051612be061206682611ffc565b81815260059190911b82018301908381019087831115612bff57600080fd5b928401925b82841015612c26578351612c1781612020565b82529284019290840190612c04565b979650505050505050565b600082612c67577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b600060208284031215612c7e57600080fd5b815167ffffffffffffffff80821115612c9657600080fd5b818401915084601f830112612caa57600080fd5b815181811115612cbc57612cbc611f32565b612ced60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601611fad565b9150808252856020828501011115612d0457600080fd5b612d158160208401602086016121fa565b50949350505050565b600060208284031215612d3057600080fd5b815160ff811681146128ae57600080fd5b600060a08284031215612d5357600080fd5b60405160a0810181811067ffffffffffffffff82111715612d7657612d76611f32565b6040528251612d8481612020565b8152612d9260208401612a5e565b60208201526040830151612da5816128b5565b60408201526060830151612db8816128b5565b6060820152608083015162ffffff81168114612dd357600080fd5b60808201529392505050565b600060208284031215612df157600080fd5b815180600b0b81146128ae57600080fd5b600080600060608486031215612e1757600080fd5b8351925060208401519150604084015190509250925092565b600080600060608486031215612e4557600080fd5b835161ffff81168114612e5757600080fd5b6020850151909350612e68816128b5565b6040850151909250612e7981612020565b80915050925092509256fea26469706673582212207d02a6c0104228ee1057537f774fe9616b8496b72d0314229b7f6c333ab5cca164736f6c634300080a0033", 712 | "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100725760003560e01c806369a92ea31161005057806369a92ea314610129578063854c87271461015e5780638f863b2e1461017e57600080fd5b8063340d059a146100775780633d77bf7f146100a05780634c0cf6f714610109575b600080fd5b61008a610085366004612149565b6101a6565b604051610097919061262e565b60405180910390f35b6100b36100ae3660046126ae565b6102a7565b6040516100979190600060e082019050825182526020830151602083015260408301516040830152606083015160608301526080830151608083015260a083015160a083015260c083015160c083015292915050565b61011c61011736600461270d565b6107b3565b604051610097919061275d565b6101507f000000000000000000000000000000000000000000000000000000000000000081565b604051908152602001610097565b61017161016c36600461285e565b6109bf565b604051610097919061289b565b61019161018c3660046128c7565b611014565b60408051928352602083019190915201610097565b6060815167ffffffffffffffff8111156101c2576101c2611f32565b60405190808252806020026020018201604052801561024257816020015b61022f6040518060c001604052806000815260200160008152602001606081526020016060815260200160008152602001600073ffffffffffffffffffffffffffffffffffffffff1681525090565b8152602001906001900390816101e05790505b50905060005b82518110156102a15761027383828151811061026657610266612908565b60200260200101516109bf565b82828151811061028557610285612908565b60200260200101819052508061029a90612966565b9050610248565b50919050565b6102e76040518060e00160405280600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b81516040517f734c938f0000000000000000000000000000000000000000000000000000000081526002600482015260009073ffffffffffffffffffffffffffffffffffffffff83169063734c938f90602401602060405180830381865afa158015610357573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061037b919061299f565b60208501516040517fb409dd9b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529192506000919083169063b409dd9b90602401602060405180830381865afa1580156103f3573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061041791906129bc565b6040517fcab65f010000000000000000000000000000000000000000000000000000000081526004810182905290915060009073ffffffffffffffffffffffffffffffffffffffff85169063cab65f0190602401602060405180830381865afa158015610488573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104ac919061299f565b9050600081905060008173ffffffffffffffffffffffffffffffffffffffff1663fd2da3396040518163ffffffff1660e01b8152600401602060405180830381865afa158015610500573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061052491906129bc565b80885260208901516040517fb74b1ed500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529192506000919087169063b74b1ed590602401602060405180830381865afa15801561059f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105c391906129d5565b905060008373ffffffffffffffffffffffffffffffffffffffff16631f68f20a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610612573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061063691906129bc565b905060008473ffffffffffffffffffffffffffffffffffffffff1663a62b75a86040518163ffffffff1660e01b8152600401602060405180830381865afa158015610685573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106a991906129bc565b6106b390856129f2565b6106bd9083612a2f565b905060008573ffffffffffffffffffffffffffffffffffffffff1663d0134cb76040518163ffffffff1660e01b8152600401602060405180830381865afa15801561070c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061073091906129bc565b61073e8663ffffffff612a47565b61074891906129f2565b6107529083612a2f565b905061076583600063ffffffff87611014565b60808d015260208c015261077f828663ffffffff87611014565b60a08d015260408c01526107998163ffffffff8087611014565b60c08d015260608c015250989a9950505050505050505050565b6040517f734c938f00000000000000000000000000000000000000000000000000000000815260056004820152606090839060009073ffffffffffffffffffffffffffffffffffffffff83169063734c938f90602401602060405180830381865afa158015610826573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061084a919061299f565b9050835167ffffffffffffffff81111561086657610866611f32565b6040519080825280602002602001820160405280156108a657816020015b6040805160208101909152606081528152602001906001900390816108845790505b50925060005b84518110156109b6578173ffffffffffffffffffffffffffffffffffffffff1663dec82caf8683815181106108e3576108e3612908565b60200260200101516040518263ffffffff1660e01b8152600401610923919073ffffffffffffffffffffffffffffffffffffffff91909116815260200190565b6000604051808303816000875af1158015610942573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526109889190810190612a73565b84828151811061099a5761099a612908565b6020908102919091010151526109af81612966565b90506108ac565b50505092915050565b610a0e6040518060c001604052806000815260200160008152602001606081526020016060815260200160008152602001600073ffffffffffffffffffffffffffffffffffffffff1681525090565b42815243602082015281516040517f734c938f0000000000000000000000000000000000000000000000000000000081526002600482015260009073ffffffffffffffffffffffffffffffffffffffff83169063734c938f90602401602060405180830381865afa158015610a87573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610aab919061299f565b6040517f734c938f0000000000000000000000000000000000000000000000000000000081526005600482015290915060009073ffffffffffffffffffffffffffffffffffffffff84169063734c938f90602401602060405180830381865afa158015610b1c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b40919061299f565b602086015190915060609073ffffffffffffffffffffffffffffffffffffffff1615610c235760208601516040517fdec82caf00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529083169063dec82caf906024016000604051808303816000875af1158015610bda573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610c209190810190612a73565b90505b8560400151518151610c359190612a2f565b67ffffffffffffffff811115610c4d57610c4d611f32565b604051908082528060200260200182016040528015610c8657816020015b610c73611df4565b815260200190600190039081610c6b5790505b50604086015260005b8151811015610d3657600086604001518281518110610cb057610cb0612908565b60200260200101519050828281518110610ccc57610ccc612908565b60209081029190910101515173ffffffffffffffffffffffffffffffffffffffff1681528251839083908110610d0457610d04612908565b602002602001015160200151816103400181905250610d25888287876110d6565b50610d2f81612966565b9050610c8f565b5080515b8660400151518251610d4c9190612a2f565b811015610dde576000825182610d629190612a47565b9050600087604001518381518110610d7c57610d7c612908565b6020026020010151905088604001518281518110610d9c57610d9c612908565b602090810291909101015173ffffffffffffffffffffffffffffffffffffffff168152610dcb898288886110d6565b505080610dd790612966565b9050610d3a565b50602086015173ffffffffffffffffffffffffffffffffffffffff161561100b5760208601516040517f8ccb720b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff918216600482015290841690638ccb720b90602401600060405180830381865afa158015610e71573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610eb79190810190612b97565b606086015260208601516040517fe94f487f00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529083169063e94f487f906024016020604051808303816000875af1158015610f30573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f5491906129bc565b608086015260208601516040517fdf6b2aab00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529083169063df6b2aab90602401602060405180830381865afa158015610fcb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fef919061299f565b73ffffffffffffffffffffffffffffffffffffffff1660a08601525b50505050919050565b6000806b033b2e3c9fd0803ce80000006110486110318883612a2f565b6301e185586b033b2e3c9fd0803ce8000000611d36565b6110529190612a47565b915060008415611076578461106787896129f2565b6110719190612c31565b611079565b60005b905063ee6b280061109063ffffffff861682612a47565b61109a90836129f2565b6110a49190612c31565b90506b033b2e3c9fd0803ce80000006110c06110318383612a2f565b6110ca9190612a47565b91505094509492505050565b826000015173ffffffffffffffffffffffffffffffffffffffff166306fdde036040518163ffffffff1660e01b8152600401600060405180830381865afa158015611125573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261116b9190810190612c6c565b8360200181905250826000015173ffffffffffffffffffffffffffffffffffffffff166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa1580156111c2573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526112089190810190612c6c565b8360400181905250826000015173ffffffffffffffffffffffffffffffffffffffff1663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801561125f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112839190612d1e565b60ff16606084015282516040517f8948874900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff918216600482015290831690638948874990602401602060405180830381865afa1580156112fa573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061131e919061299f565b73ffffffffffffffffffffffffffffffffffffffff166080840181905261134457611d30565b60808301516040517f6b5e360600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff918216600482015290831690636b5e360690602401602060405180830381865afa1580156113b6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113da919061299f565b73ffffffffffffffffffffffffffffffffffffffff90811660a085015283516040517ffd7948d400000000000000000000000000000000000000000000000000000000815290821660048201529083169063fd7948d490602401602060405180830381865afa158015611451573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611475919061299f565b73ffffffffffffffffffffffffffffffffffffffff90811660c085015283516040517f4e9cfac20000000000000000000000000000000000000000000000000000000081529082166004820152600091841690634e9cfac29060240160a060405180830381865afa1580156114ee573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115129190612d41565b60e085015250825184516040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529116906370a0823190602401602060405180830381865afa158015611588573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115ac91906129bc565b83610100018181525050826080015173ffffffffffffffffffffffffffffffffffffffff1663fea61faa6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611605573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061162991906129bc565b836101200181815250508260a0015173ffffffffffffffffffffffffffffffffffffffff166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611682573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116a691906129bc565b83610140018181525050826080015173ffffffffffffffffffffffffffffffffffffffff1663e73277d06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156116ff573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061172391906129bc565b61016084015282516040517fb74b1ed500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529083169063b74b1ed590602401602060405180830381865afa158015611798573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117bc91906129d5565b63ffffffff1661018084015282516040517f7c2c69c000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9182166004820152600091841690637c2c69c090602401602060405180830381865afa158015611839573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061185d9190612ddf565b600b0b905061187d81856101400151866101200151876101800151611014565b6101c08601526101a08501525082516040517f9693fa6b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff918216600482015290821690639693fa6b906024016060604051808303816000875af11580156118fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061191f9190612e02565b6102208601526102008501526101e084015282516040517f546422d600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529083169063546422d690602401606060405180830381865afa1580156119a0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119c49190612e30565b73ffffffffffffffffffffffffffffffffffffffff90811661028087015263ffffffff90911661026086015261ffff909116610240850152602085015116611a0b57611d30565b825160208501516040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529116906370a0823190602401602060405180830381865afa158015611a7e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611aa291906129bc565b6102a0840152608083015160208501516040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529116906370a0823190602401602060405180830381865afa158015611b1e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b4291906129bc565b6102e0840152608083015160208501516040517f3af9e66900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9182166004820152911690633af9e66990602401602060405180830381865afa158015611bbe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611be291906129bc565b61030084015260a083015160208501516040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff91821660048201529116906370a0823190602401602060405180830381865afa158015611c5e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c8291906129bc565b6103208401528251602085015185516040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9283166004820152908216602482015291169063dd62ed3e90604401602060405180830381865afa158015611d05573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d2991906129bc565b6102c08401525b50505050565b6000838015611dd657600184168015611d5157859250611d55565b8392505b50600283046002850494505b8415611dd0578586028687820414611d7857600080fd5b81810181811015611d8857600080fd5b8590049650506001851615611dc5578583028387820414158715151615611dae57600080fd5b81810181811015611dbe57600080fd5b8590049350505b600285049450611d61565b50611dec565b838015611de65760009250611dea565b8392505b505b509392505050565b6040805161036081018252600080825260606020808401829052838501829052818401839052608080850184905260a080860185905260c0860185905286519081018752848152918201849052948101839052908101829052928301529060e0820190815260200160008152602001600081526020016000815260200160008152602001600063ffffffff1681526020016000815260200160008152602001600081526020016000815260200160008152602001600061ffff168152602001600063ffffffff168152602001600073ffffffffffffffffffffffffffffffffffffffff1681526020016000815260200160008152602001600081526020016000815260200160008152602001611f2d60405180608001604052806000815260200160008152602001600081526020016000151581525090565b905290565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff81118282101715611f8457611f84611f32565b60405290565b6040516080810167ffffffffffffffff81118282101715611f8457611f84611f32565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715611ff457611ff4611f32565b604052919050565b600067ffffffffffffffff82111561201657612016611f32565b5060051b60200190565b73ffffffffffffffffffffffffffffffffffffffff8116811461204257600080fd5b50565b600082601f83011261205657600080fd5b8135602061206b61206683611ffc565b611fad565b82815260059290921b8401810191818101908684111561208a57600080fd5b8286015b848110156120ae5780356120a181612020565b835291830191830161208e565b509695505050505050565b6000606082840312156120cb57600080fd5b6040516060810167ffffffffffffffff82821081831117156120ef576120ef611f32565b816040528293508435915061210382612020565b90825260208401359061211582612020565b816020840152604085013591508082111561212f57600080fd5b5061213c85828601612045565b6040830152505092915050565b6000602080838503121561215c57600080fd5b823567ffffffffffffffff8082111561217457600080fd5b818501915085601f83011261218857600080fd5b813561219661206682611ffc565b81815260059190911b830184019084810190888311156121b557600080fd5b8585015b838110156121ed578035858111156121d15760008081fd5b6121df8b89838a01016120b9565b8452509186019186016121b9565b5098975050505050505050565b60005b838110156122155781810151838201526020016121fd565b83811115611d305750506000910152565b6000815180845261223e8160208601602086016121fa565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600081518084526020808501945080840160005b838110156122b657815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101612284565b509495945050505050565b600060c08084018351855260208085015181870152604080860151848289015283815180865260e09550858a019150858160051b8b0101858401935060005b828110156125da578b82037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff200184528451805173ffffffffffffffffffffffffffffffffffffffff16835261044088820151818a86015261236382860182612226565b915050878201518482038986015261237b8282612226565b9150506060808301516123928287018260ff169052565b505060808281015173ffffffffffffffffffffffffffffffffffffffff811686830152505060a08281015173ffffffffffffffffffffffffffffffffffffffff8116868301525050818b015173ffffffffffffffffffffffffffffffffffffffff8116858d0152508982015161245e8b86018273ffffffffffffffffffffffffffffffffffffffff8151168252602081015115156020830152604081015163ffffffff8082166040850152806060840151166060850152505062ffffff60808201511660808301525050565b50610100820151610180818187015261012084015191506101a0828188015261014085015192506101c0838189015261016086015193506101e084818a015283870151945061020093506124b9848a018663ffffffff169052565b918601516102208981019190915290860151610240808a019190915291860151610260808a019190915292860151610280808a0191909152908601516102a0808a0191909152918601519350916102c090612519828a018661ffff169052565b86015193506102e06125328982018663ffffffff169052565b9286015193506103009261255d8985018673ffffffffffffffffffffffffffffffffffffffff169052565b918601516103208981019190915290860151610340808a01919091529186015161036089015291850151610380880152908401516103a08701529092015180516103c086015260208101516103e0860152604081015161040086015260600151151561042090940193909352509386019392860192600101612300565b5060608a015197508a810360608c01526125f48189612270565b9750505050505050506080830151608085015260a0830151611dec60a086018273ffffffffffffffffffffffffffffffffffffffff169052565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b828110156126a1577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc088860301845261268f8583516122c1565b94509285019290850190600101612655565b5092979650505050505050565b6000604082840312156126c057600080fd5b6040516040810181811067ffffffffffffffff821117156126e3576126e3611f32565b60405282356126f181612020565b8152602083013561270181612020565b60208201529392505050565b6000806040838503121561272057600080fd5b823561272b81612020565b9150602083013567ffffffffffffffff81111561274757600080fd5b61275385828601612045565b9150509250929050565b60006020808301818452808551808352604092508286019150828160051b8701018488016000805b8481101561284f578984037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00186528251518885528051898601819052908901908390898701905b8083101561283a578351805173ffffffffffffffffffffffffffffffffffffffff1683528c01516128238d8401828051825260208101516020830152604081015160408301526060810151151560608301525050565b5060a0820191508b840193506001830192506127cd565b50978a01979550505091870191600101612785565b50919998505050505050505050565b60006020828403121561287057600080fd5b813567ffffffffffffffff81111561288757600080fd5b612893848285016120b9565b949350505050565b6020815260006128ae60208301846122c1565b9392505050565b63ffffffff8116811461204257600080fd5b600080600080608085870312156128dd57600080fd5b84359350602085013592506040850135915060608501356128fd816128b5565b939692955090935050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82141561299857612998612937565b5060010190565b6000602082840312156129b157600080fd5b81516128ae81612020565b6000602082840312156129ce57600080fd5b5051919050565b6000602082840312156129e757600080fd5b81516128ae816128b5565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615612a2a57612a2a612937565b500290565b60008219821115612a4257612a42612937565b500190565b600082821015612a5957612a59612937565b500390565b80518015158114612a6e57600080fd5b919050565b60006020808385031215612a8657600080fd5b825167ffffffffffffffff811115612a9d57600080fd5b8301601f81018513612aae57600080fd5b8051612abc61206682611ffc565b81815260a09182028301840191848201919088841115612adb57600080fd5b938501935b83851015612b8b5784890381811215612af95760008081fd5b612b01611f61565b8651612b0c81612020565b815260807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08301811315612b405760008081fd5b612b48611f8a565b92508888015183526040808901518a8501526060808a015182860152612b6f838b01612a5e565b9085015250508088019190915283529384019391850191612ae0565b50979650505050505050565b60006020808385031215612baa57600080fd5b825167ffffffffffffffff811115612bc157600080fd5b8301601f81018513612bd257600080fd5b8051612be061206682611ffc565b81815260059190911b82018301908381019087831115612bff57600080fd5b928401925b82841015612c26578351612c1781612020565b82529284019290840190612c04565b979650505050505050565b600082612c67577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b600060208284031215612c7e57600080fd5b815167ffffffffffffffff80821115612c9657600080fd5b818401915084601f830112612caa57600080fd5b815181811115612cbc57612cbc611f32565b612ced60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601611fad565b9150808252856020828501011115612d0457600080fd5b612d158160208401602086016121fa565b50949350505050565b600060208284031215612d3057600080fd5b815160ff811681146128ae57600080fd5b600060a08284031215612d5357600080fd5b60405160a0810181811067ffffffffffffffff82111715612d7657612d76611f32565b6040528251612d8481612020565b8152612d9260208401612a5e565b60208201526040830151612da5816128b5565b60408201526060830151612db8816128b5565b6060820152608083015162ffffff81168114612dd357600080fd5b60808201529392505050565b600060208284031215612df157600080fd5b815180600b0b81146128ae57600080fd5b600080600060608486031215612e1757600080fd5b8351925060208401519150604084015190509250925092565b600080600060608486031215612e4557600080fd5b835161ffff81168114612e5757600080fd5b6020850151909350612e68816128b5565b6040850151909250612e7981612020565b80915050925092509256fea26469706673582212207d02a6c0104228ee1057537f774fe9616b8496b72d0314229b7f6c333ab5cca164736f6c634300080a0033", 713 | "linkReferences": {}, 714 | "deployedLinkReferences": {} 715 | } 716 | -------------------------------------------------------------------------------- /src/components/LiquidityChart/LiquidityChart.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { XAxis, YAxis, Tooltip, ReferenceLine, BarChart, Bar } from "recharts"; 3 | import { DefaultTooltipContent } from "recharts/lib/component/DefaultTooltipContent"; 4 | import { Decimal } from "decimal.js"; 5 | 6 | import { numberFormatText } from "../../utils"; 7 | const CustomTooltip = (props) => { 8 | if (props.payload[0] != null) { 9 | const payload = props.payload[0].payload; 10 | const newPayload = [ 11 | // ...props.payload, 12 | { 13 | name: "token amount", 14 | value: payload.tokenAmount + " " + payload.symbol, 15 | }, 16 | { 17 | name: "USD value", 18 | value: numberFormatText(payload.usdValue), 19 | }, 20 | ]; 21 | 22 | return ; 23 | } 24 | 25 | // we just render the default 26 | return ; 27 | }; 28 | 29 | export const LiquidityChart = ({ data, tick, tickSpacing, width, height }) => ( 30 | 41 | 42 | { 44 | return numberFormatText(tick); 45 | }} 46 | // label={{ value: 'USD', angle: -90, position: 'insideLeft' }} 47 | /> 48 | "Tick " + v} 52 | formatter={(value, name) => [ 53 | name === "liquidity" ? new Decimal(value).toFixed() : value, 54 | name, 55 | ]} 56 | /> 57 | 58 | 62 | 63 | ); 64 | -------------------------------------------------------------------------------- /src/components/LiquidityChart/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/euler-oracle-tools/63cc9d8140f8dd72a6bb41f125d290d5737996ab/src/components/LiquidityChart/index.js -------------------------------------------------------------------------------- /src/components/LiquidityReport/LiquidityReport.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Box from "@mui/material/Box"; 3 | import Table from "@mui/material/Table"; 4 | import TableBody from "@mui/material/TableBody"; 5 | import TableCell from "@mui/material/TableCell"; 6 | import TableContainer from "@mui/material/TableContainer"; 7 | import TableHead from "@mui/material/TableHead"; 8 | import TableRow from "@mui/material/TableRow"; 9 | import Paper from "@mui/material/Paper"; 10 | import CircularProgress from "@mui/material/CircularProgress"; 11 | import Grid from "@mui/material/Grid"; 12 | import Typography from "@mui/material/Typography"; 13 | import { 14 | XAxis, 15 | YAxis, 16 | CartesianGrid, 17 | Tooltip, 18 | Legend, 19 | BarChart, 20 | Bar, 21 | } from "recharts"; 22 | import { numberFormatText, getCostOfAttack } from "../../utils"; 23 | 24 | export const LiquidityReport = ({ 25 | loading, 26 | progress, 27 | data, 28 | collateralFactor, 29 | borrowFactor, 30 | usdcMarketConfig, 31 | currPrice, 32 | ethPrice, 33 | token, 34 | window, 35 | }) => { 36 | return ( 37 | <> 38 | {loading && ( 39 | 45 | 46 | 58 | 63 | {`${Math.round(progress)}%`} 64 | 65 | 66 | 67 | )} 68 | {!loading && ( 69 | 70 | 71 | 72 | 73 | TWAP Window: 74 | 75 | 76 | {window} 77 | 78 | 79 | Collateral Factor: 80 | 81 | 82 | {collateralFactor} 83 | 84 | 85 | Borrow Factor: 86 | 87 | 88 | {borrowFactor} 89 | 90 | 91 | TWAP Pump Impact Target: 92 | 93 | 94 | {numberFormatText( 95 | (1 / (collateralFactor * usdcMarketConfig.borrowFactor) - 1) * 96 | 100 97 | )} 98 | % 99 | 100 | 101 | TWAP Dump Impact Target: 102 | 103 | 104 | {numberFormatText( 105 | (1 / (collateralFactor * usdcMarketConfig.borrowFactor) - 1) * 106 | 100 107 | )} 108 | % 109 | 110 | 111 | 112 | 113 | 114 | 115 | BLOCKS 116 | 117 | {String.fromCharCode(8657)} VALUE USD 118 | 119 | 120 | {String.fromCharCode(8657)} COST USD 121 | 122 | 123 | {String.fromCharCode(8657)} TOTAL COST USD 124 | 125 | 126 | {String.fromCharCode(8659)} VALUE USD 127 | 128 | 129 | {String.fromCharCode(8659)} COST USD 130 | 131 | 132 | {String.fromCharCode(8659)} TOTAL COST USD 133 | 134 | 135 | 136 | 137 | {data.map((row, i) => ( 138 | 144 | 145 | {i + 1} 146 | 147 | 148 | {row.pump.best 149 | ? numberFormatText(row.pump.best.value) 150 | : row.pump} 151 | 152 | 153 | {row.pump.best 154 | ? numberFormatText( 155 | getCostOfAttack( 156 | row.pump.best, 157 | currPrice, 158 | ethPrice, 159 | token 160 | ) 161 | ) 162 | : row.pump} 163 | 164 | 165 | {row.pump.best 166 | ? numberFormatText( 167 | getCostOfAttack( 168 | row.pump.best, 169 | currPrice, 170 | ethPrice, 171 | token 172 | ) * 173 | (i + 1) 174 | ) 175 | : row.pump} 176 | 177 | 178 | {row.dump.best 179 | ? numberFormatText(row.dump.best.value) 180 | : row.dump} 181 | 182 | 183 | {row.dump.best 184 | ? numberFormatText( 185 | getCostOfAttack( 186 | row.dump.best, 187 | currPrice, 188 | ethPrice, 189 | token 190 | ) 191 | ) 192 | : row.dump} 193 | 194 | 195 | {row.dump.best 196 | ? numberFormatText( 197 | getCostOfAttack( 198 | row.dump.best, 199 | currPrice, 200 | ethPrice, 201 | token 202 | ) * 203 | (i + 1) 204 | ) 205 | : row.dump} 206 | 207 | 208 | ))} 209 | 210 |
211 |
212 |
213 | 214 | ({ 218 | blocks: i + 1, 219 | "total pump value": row.pump.best 220 | ? row.pump.best.value * (i + 1) 221 | : 0, 222 | "total pump cost": row.pump.best 223 | ? getCostOfAttack(row.pump.best, currPrice, ethPrice, token) * 224 | (i + 1) 225 | : 0, 226 | }))} 227 | margin={{ 228 | top: 5, 229 | right: 30, 230 | left: 40, 231 | bottom: 5, 232 | }} 233 | > 234 | 235 | 241 | dataMax * 2]} 245 | tickFormatter={(tick) => { 246 | return numberFormatText(tick); 247 | }} 248 | /> 249 | v + " Blocks"} 251 | formatter={(value, name) => [numberFormatText(value), name]} 252 | /> 253 | 254 | 255 | 256 | 257 | row.dump.best) 262 | .map((row, i) => ({ 263 | blocks: row.blocks, 264 | "total dump value": row.dump.best 265 | ? row.dump.best.value * (i + 1) 266 | : 0, 267 | "total dump cost": row.dump.best 268 | ? getCostOfAttack( 269 | row.dump.best, 270 | currPrice, 271 | ethPrice, 272 | token 273 | ) * 274 | (i + 1) 275 | : 0, 276 | }))} 277 | margin={{ 278 | top: 5, 279 | right: 30, 280 | left: 40, 281 | bottom: 5, 282 | }} 283 | > 284 | 285 | 291 | dataMax * 2]} 295 | tickFormatter={(tick) => { 296 | return numberFormatText(tick); 297 | }} 298 | /> 299 | v + " Blocks"} 301 | formatter={(value, name) => [numberFormatText(value), name]} 302 | /> 303 | 304 | 305 | 306 | 307 | {/* */} 314 | 315 |
316 | )} 317 | 318 | ); 319 | }; 320 | -------------------------------------------------------------------------------- /src/components/LiquidityReport/index.js: -------------------------------------------------------------------------------- 1 | export { LiquidityReport } from "./LiquidityReport"; -------------------------------------------------------------------------------- /src/components/Main/Main.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, useRef } from "react"; 2 | import axios from "axios"; 3 | import Box from "@mui/material/Box"; 4 | import InputLabel from "@mui/material/InputLabel"; 5 | import MenuItem from "@mui/material/MenuItem"; 6 | import FormControl from "@mui/material/FormControl"; 7 | import Select from "@mui/material/Select"; 8 | import TextField from "@mui/material/TextField"; 9 | import Table from "@mui/material/Table"; 10 | import TableBody from "@mui/material/TableBody"; 11 | import TableCell from "@mui/material/TableCell"; 12 | import TableContainer from "@mui/material/TableContainer"; 13 | import TableHead from "@mui/material/TableHead"; 14 | import TableRow from "@mui/material/TableRow"; 15 | import Paper from "@mui/material/Paper"; 16 | import Button from "@mui/material/Button"; 17 | import IconButton from "@mui/material/IconButton"; 18 | import DownloadIcon from "@mui/icons-material/Download"; 19 | import PlayArrowIcon from "@mui/icons-material/PlayArrow"; 20 | import CircularProgress from "@mui/material/CircularProgress"; 21 | import Autocomplete from "@mui/material/Autocomplete"; 22 | import Dialog from "@mui/material/Dialog"; 23 | import DialogActions from "@mui/material/DialogActions"; 24 | import DialogContent from "@mui/material/DialogContent"; 25 | import DialogContentText from "@mui/material/DialogContentText"; 26 | import DialogTitle from "@mui/material/DialogTitle"; 27 | import Link from "@mui/material/Link"; 28 | import Card from "@mui/material/Card"; 29 | import CardContent from "@mui/material/CardContent"; 30 | import Grid from "@mui/material/Grid"; 31 | import Divider from "@mui/material/Divider"; 32 | 33 | import { CSVLink } from "react-csv"; 34 | 35 | import { sortBy } from "lodash"; 36 | import { matchSorter } from "match-sorter"; 37 | import { Decimal } from "decimal.js"; 38 | import { utils } from "ethers"; 39 | 40 | // import reportJson from '../../report.json' 41 | 42 | import { PriceImpactChart } from "../PriceImpactChart"; 43 | import { LiquidityReport } from "../LiquidityReport"; 44 | 45 | import { 46 | getSlot0, 47 | numberFormatText, 48 | formatPrice, 49 | getPoolFees, 50 | computeUniV3PoolAddress, 51 | sqrtPriceX96ToPrice, 52 | isInverted, 53 | getMarketConfig, 54 | getTwapTargetRatio, 55 | getTwapAfterAttack, 56 | getMinMaxTargetTwapSpot, 57 | getCostOfAttack, 58 | getPumpAndDump, 59 | binarySearchTradeValues, 60 | MAX_TICK_PRICE, 61 | MIN_TICK_PRICE, 62 | USDC_ADDRESS, 63 | WETH_ADDRESS, 64 | // getLiquidityProfile, 65 | // getLiquidityStats, 66 | // parseLiquidityRange, 67 | } from "../../utils"; 68 | 69 | 70 | export const Main = () => { 71 | const [tokenList, setTokenList] = useState([]); 72 | const [tokenName, setTokenName] = useState("USD Coin"); 73 | 74 | const [fee, setFee] = useState(3000); 75 | const [ethPrice, setEthPrice] = useState(0); 76 | const [trades, setTrades] = useState(); 77 | const [currPrice, setCurrPrice] = useState(); 78 | const [currSqrtPriceX96, setCurrSqrtPriceX96] = useState(); 79 | const [currTick, setCurrTick] = useState(); 80 | const [cardinality, setCardinality] = useState(); 81 | const [poolFees, setPoolFees] = useState([]); 82 | 83 | const [targetPriceImpact, setTargetPriceImpact] = useState(90); 84 | const [targetPriceImpactLoading, setTargetPriceImpactLoading] = 85 | useState(false); 86 | const [targetPriceImpactValue, setTargetPriceImpactValue] = useState(); 87 | 88 | const [targetEthPrice, setTargetEthPrice] = useState(""); 89 | const [targetUsdPrice, setTargetUsdPrice] = useState(""); 90 | const [targetPriceLoading, setTargetPriceLoading] = useState(false); 91 | const [targetPriceValue, setTargetPriceValue] = useState(); 92 | 93 | const [window, setWindow] = useState(144); 94 | const [attackBlocks, setAttackBlocks] = useState(1); 95 | const [targetEthTwap, setTargetEthTwap] = useState(""); 96 | const [targetUsdTwap, setTargetUsdTwap] = useState(""); 97 | const [targetTwapLoading, setTargetTwapLoading] = useState(false); 98 | const [targetTwapValue, setTargetTwapValue] = useState(); 99 | const [targetTwapSpot, setTargetTwapSpot] = useState(""); 100 | 101 | const [reportBorrowFactor, setReportBorrowFactor] = useState(0.91); 102 | const [reportCollateralFactor, setReportCollateralFactor] = useState(0.88); 103 | 104 | const [error, setError] = useState(); 105 | const [errorOpen, setErrorOpen] = useState(false); 106 | 107 | const [usdcMarketConfig, setUsdcMarketConfig] = useState(); 108 | const [marketConfig, setMarketConfig] = useState(); 109 | const [reportOpen, setReportOpen] = useState(false); 110 | const [reportData, setReportData] = useState(); 111 | const [reportLoading, setReportLoading] = useState(true); 112 | const [reportProgress, setReportProgress] = useState(0); 113 | 114 | const [liquidityProfile, setLiquidityProfile] = useState(); 115 | const [liquidityChartData, setLiquidityChartData] = useState(); 116 | const [liquidityStats, setLiquidityStats] = useState(); 117 | 118 | // todo fix canceling 119 | const cancelPriceImpactSearch = useRef(() => {}); 120 | const cancelPriceSearch = useRef(() => {}); 121 | const cancelTwapSearch = useRef(() => {}); 122 | const csvLink = useRef(); 123 | 124 | const token = 125 | tokenList.length > 0 && tokenList.find((t) => t.name === tokenName); 126 | 127 | const amountsUSD = [ 128 | 100_000, 200_000, 300_000, 400_000, 500_000, 600_000, 700_000, 800_000, 129 | 900_000, 1_000_000, 2_000_000, 3_000_000, 4_000_000, 5_000_000, 6_000_000, 130 | 7_000_000, 8_000_000, 9_000_000, 10_000_000, 131 | ]; 132 | 133 | let minTargetTwapSpotPercentage = "-"; 134 | let maxTargetTwapSpotPercentage = "-"; 135 | let maxTargetTwapSpot; 136 | let minTargetTwapSpot; 137 | let twapTargetExceedsMax = false; 138 | 139 | if (currPrice && ethPrice && currTick && attackBlocks && window) { 140 | ({ 141 | minTargetTwapSpot, 142 | maxTargetTwapSpot, 143 | minTargetTwapSpotPercentage, 144 | maxTargetTwapSpotPercentage, 145 | } = getMinMaxTargetTwapSpot(currPrice, attackBlocks, window, token)); 146 | 147 | if (targetEthTwap) { 148 | const t = new Decimal(targetEthTwap); 149 | twapTargetExceedsMax = t.lt(minTargetTwapSpot) || t.gt(maxTargetTwapSpot); 150 | } 151 | } 152 | 153 | const getStandardTradesTable = () => { 154 | return amountsUSD.map((a) => { 155 | const pump = trades.pump.find((t) => t.value === a); 156 | const dump = trades.dump.find((t) => t.value === a); 157 | return { pump, dump }; 158 | }); 159 | }; 160 | 161 | const getStandardTrades = () => { 162 | return getStandardTradesTable().reduce( 163 | (accu, t) => { 164 | accu.pump.push(t.pump); 165 | accu.dump.push(t.dump); 166 | return accu; 167 | }, 168 | { pump: [], dump: [] } 169 | ); 170 | }; 171 | 172 | useEffect(() => { 173 | Promise.all([ 174 | axios.get( 175 | "https://raw.githubusercontent.com/euler-xyz/euler-tokenlist/master/euler-tokenlist.json" 176 | ), 177 | axios.get( 178 | `https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD` 179 | ), 180 | // getMarketConfig(USDC_ADDRESS), 181 | ]).then(([result1, result2, result3]) => { 182 | setTokenList(sortBy(result1.data.tokens, "symbol")); 183 | setEthPrice(Number(result2.data.USD)); 184 | // setUsdcMarketConfig(result3); 185 | setUsdcMarketConfig({ borrowFactor: 0.95 }); 186 | }); 187 | }, []); 188 | 189 | useEffect(() => { 190 | if (!tokenList.length || !ethPrice) return; 191 | 192 | getPoolFees(token.address).then((fees) => { 193 | setPoolFees(fees); 194 | setFee(fees.includes(3000) ? 3000 : fees[0]); 195 | }); 196 | }, [tokenName, tokenList, ethPrice]); 197 | 198 | useEffect(() => { 199 | if (!tokenList.length || !ethPrice || !poolFees.includes(fee)) return; 200 | 201 | const getMarket = async () => { 202 | const [{ price, sqrtPriceX96, tick, observationCardinality }, config] = 203 | await Promise.all([ 204 | getSlot0(token, fee), 205 | // getMarketConfig(token.address), 206 | ]); 207 | 208 | // const profile = await getLiquidityProfile(token, fee); 209 | 210 | setCurrPrice(price); 211 | setCurrSqrtPriceX96(sqrtPriceX96.toString()); 212 | setCurrTick(tick); 213 | setCardinality(observationCardinality); 214 | setTargetEthPrice(formatPrice(price, token)); 215 | setTargetUsdPrice(formatPrice(price, token) * ethPrice); 216 | setTargetEthTwap(formatPrice(price, token)); 217 | setTargetUsdTwap(formatPrice(price, token) * ethPrice); 218 | // setMarketConfig(config); 219 | 220 | // setLiquidityProfile(profile); 221 | // setLiquidityChartData(parseLiquidityRange(profile, currTick, token, ethPrice, price, 300, 300)); 222 | }; 223 | getMarket(); 224 | }, [tokenName, fee, poolFees, tokenList, ethPrice]); 225 | 226 | useEffect(() => { 227 | if (!tokenList.length || !ethPrice || !currPrice || !poolFees.includes(fee)) 228 | return; 229 | setTrades(null); 230 | Promise.all( 231 | amountsUSD.map((a) => getPumpAndDump(currPrice, token, fee, ethPrice, a)) 232 | ) 233 | .then((res) => { 234 | setTrades({ 235 | pump: res.map((r) => r.pump), 236 | dump: res.map((r) => r.dump), 237 | }); 238 | }) 239 | .catch((e) => { 240 | let reason = (e.reason === "AS" || e.reason === "SPL") ? ": no liquidity" : ""; 241 | if (e.reason === "TF") reason = ": transfer failed"; 242 | handleError("Failed to fetch quotes" + reason); 243 | }); 244 | }, [ 245 | tokenName, 246 | fee, 247 | poolFees, 248 | tokenList, 249 | ethPrice, 250 | currPrice && currPrice.toString(), 251 | ]); 252 | 253 | const onTargetPriceImpact = () => { 254 | cancelPriceImpactSearch.current(); 255 | 256 | setTargetPriceImpactLoading(true); 257 | 258 | const targetDecimal = new Decimal(targetPriceImpact); 259 | const { promise, cancel } = binarySearchTradeValues( 260 | currPrice, 261 | currSqrtPriceX96, 262 | token, 263 | fee, 264 | ethPrice, 265 | targetDecimal, 266 | "priceImpact" 267 | ); 268 | cancelPriceImpactSearch.current = cancel; 269 | 270 | promise 271 | .then(([pump, dump]) => { 272 | resetResults(); 273 | setTargetPriceImpactValue({ pump: pump.best, dump: dump.best }); 274 | 275 | const standardTrades = getStandardTrades(); 276 | setTrades({ 277 | pump: sortBy(standardTrades.pump.concat(pump.trades), "value"), 278 | dump: sortBy(standardTrades.dump.concat(dump.trades), "value"), 279 | }); 280 | setTargetPriceImpactLoading(false); 281 | }) 282 | .catch((e) => { 283 | handleError(e); 284 | }); 285 | return () => cancelPriceImpactSearch.current(); 286 | }; 287 | 288 | const onTargetPrice = () => { 289 | cancelPriceSearch.current(); 290 | 291 | if (targetEthPrice === formatPrice(currPrice, token)) { 292 | handleError( 293 | "Please enter a target spot price which is different from current price." 294 | ); 295 | return; 296 | } 297 | 298 | setTargetPriceLoading(true); 299 | const targetDecimal = new Decimal(targetEthPrice); 300 | const { promise, cancel } = binarySearchTradeValues( 301 | currPrice, 302 | currSqrtPriceX96, 303 | token, 304 | fee, 305 | ethPrice, 306 | targetDecimal, 307 | "price" 308 | ); 309 | cancelPriceSearch.current = cancel; 310 | 311 | promise 312 | .then(([pump, dump]) => { 313 | resetResults(); 314 | setTargetPriceValue({ 315 | pump: pump && pump.best, 316 | dump: dump && dump.best, 317 | }); 318 | 319 | const standardTrades = getStandardTrades(); 320 | setTrades({ 321 | pump: sortBy( 322 | standardTrades.pump.concat(pump ? pump.trades : []), 323 | "value" 324 | ), 325 | dump: sortBy( 326 | standardTrades.dump.concat(dump ? dump.trades : []), 327 | "value" 328 | ), 329 | }); 330 | setTargetPriceLoading(false); 331 | }) 332 | .catch((e) => { 333 | setTargetPriceLoading(false); 334 | handleError(e); 335 | }); 336 | 337 | return () => cancelPriceSearch.current(); 338 | }; 339 | 340 | const onTargetTwap = () => { 341 | cancelTwapSearch.current(); 342 | 343 | // TODO helper 344 | let currPriceDecimal = new Decimal(utils.formatEther(currPrice.toString())); 345 | const inverted = isInverted(token.address); 346 | const targetEthTwapScaled = Decimal.mul( 347 | targetEthTwap, 348 | Decimal.pow(10, 18 - token.decimals) 349 | ); 350 | 351 | const target = getTwapTargetRatio( 352 | targetEthTwapScaled, 353 | token, 354 | currPriceDecimal, 355 | window, 356 | attackBlocks 357 | ); 358 | 359 | setTargetTwapSpot( 360 | formatPrice(sqrtPriceX96ToPrice(target.toFixed(), inverted), token) 361 | ); 362 | // console.log("target sqrtPrice: ", target.toFixed()); 363 | // console.log( 364 | // "target price ETH:", 365 | // formatPrice(sqrtPriceX96ToPrice(target.toFixed(), inverted), token) 366 | // ); 367 | // console.log( 368 | // "target price USD:", 369 | // formatPrice(sqrtPriceX96ToPrice(target.toFixed(), inverted), token) * 370 | // ethPrice 371 | // ); 372 | 373 | const { promise, cancel } = binarySearchTradeValues( 374 | currPrice, 375 | currSqrtPriceX96, 376 | token, 377 | fee, 378 | ethPrice, 379 | target, 380 | "sqrtPriceX96After" 381 | ); 382 | cancelPriceSearch.current = cancel; 383 | setTargetTwapLoading(true); 384 | promise 385 | .then(([pump, dump]) => { 386 | resetResults(); 387 | setTargetTwapValue({ 388 | pump: pump && pump.best, 389 | dump: dump && dump.best, 390 | }); 391 | 392 | const standardTrades = getStandardTrades(); 393 | setTrades({ 394 | pump: sortBy( 395 | standardTrades.pump.concat(pump ? pump.trades : []), 396 | "value" 397 | ), 398 | dump: sortBy( 399 | standardTrades.dump.concat(dump ? dump.trades : []), 400 | "value" 401 | ), 402 | }); 403 | }) 404 | .catch((e) => { 405 | console.log("e: ", e); 406 | handleError(e); 407 | }) 408 | .finally(() => { 409 | setTargetTwapLoading(false); 410 | }); 411 | 412 | return () => cancelTwapSearch.current(); 413 | }; 414 | 415 | const onReport = () => { 416 | setReportOpen(true); 417 | setReportLoading(true); 418 | 419 | const runReport = async () => { 420 | const currPriceDecimal = new Decimal( 421 | utils.formatEther(currPrice.toString()) 422 | ); 423 | const breakEvenPumpTwapChange = 424 | 1 / (reportCollateralFactor * usdcMarketConfig.borrowFactor) - 1; 425 | const breakEvenDumpTwapChange = breakEvenPumpTwapChange; // 1 / (reportBorrowFactor * usdcMarketConfig.collateralFactor) - 1; 426 | 427 | let progress = 0; 428 | const attackBlocksReport = async (attackB) => { 429 | const p = utils.formatEther(currPrice); 430 | const maxTwapPump = getTwapAfterAttack( 431 | MAX_TICK_PRICE, 432 | p, 433 | window, 434 | attackB 435 | ); 436 | const maxTwapDump = getTwapAfterAttack( 437 | MIN_TICK_PRICE, 438 | p, 439 | window, 440 | attackB 441 | ); 442 | const targetTwapPump = currPriceDecimal.mul( 443 | 1 + breakEvenPumpTwapChange 444 | ); 445 | const targetTwapDump = currPriceDecimal.mul( 446 | 1 - breakEvenDumpTwapChange 447 | ); 448 | 449 | let pumpAction; 450 | let dumpAction; 451 | if (targetTwapPump.gt(maxTwapPump)) { 452 | pumpAction = Promise.reject("max_target"); 453 | } else { 454 | const targetRatioPump = getTwapTargetRatio( 455 | targetTwapPump, 456 | token, 457 | currPriceDecimal, 458 | window, 459 | attackB 460 | ); 461 | const { promise, cancel: cancelPump } = binarySearchTradeValues( 462 | currPrice, 463 | currSqrtPriceX96, 464 | token, 465 | fee, 466 | ethPrice, 467 | targetRatioPump, 468 | "sqrtPriceX96After" 469 | ); 470 | pumpAction = promise; 471 | 472 | // console.log("PUMP target sqrtPrice: ", targetRatioPump.toFixed()); 473 | // console.log( 474 | // "PUMP target price ETH:", 475 | // formatPrice( 476 | // sqrtPriceX96ToPrice(targetRatioPump.toFixed(), inverted), 477 | // token 478 | // ) 479 | // ); 480 | // console.log( 481 | // "PUMP target price USD:", 482 | // formatPrice( 483 | // sqrtPriceX96ToPrice(targetRatioPump.toFixed(), inverted), 484 | // token 485 | // ) * ethPrice 486 | // ); 487 | } 488 | 489 | if (targetTwapDump.lt(maxTwapDump)) { 490 | dumpAction = Promise.reject("max_target"); 491 | } else { 492 | const targetRatioDump = getTwapTargetRatio( 493 | targetTwapDump, 494 | token, 495 | currPriceDecimal, 496 | window, 497 | attackB 498 | ); 499 | let { promise, cancel: cancelDump } = binarySearchTradeValues( 500 | currPrice, 501 | currSqrtPriceX96, 502 | token, 503 | fee, 504 | ethPrice, 505 | targetRatioDump, 506 | "sqrtPriceX96After" 507 | ); 508 | dumpAction = promise; 509 | // console.log("DUMP target sqrtPrice: ", targetRatioDump.toFixed()); 510 | // console.log( 511 | // "DUMP target price ETH:", 512 | // formatPrice( 513 | // sqrtPriceX96ToPrice(targetRatioDump.toFixed(), inverted), 514 | // token 515 | // ) 516 | // ); 517 | // console.log( 518 | // "DUMP target price USD:", 519 | // formatPrice( 520 | // sqrtPriceX96ToPrice(targetRatioDump.toFixed(), inverted), 521 | // token 522 | // ) * ethPrice 523 | // ); 524 | } 525 | 526 | // todo handle cancel, improve search direction 527 | const res = await Promise.allSettled([pumpAction, dumpAction]); 528 | 529 | progress += 10; 530 | setReportProgress(progress); 531 | return res; 532 | }; 533 | try { 534 | let res = []; 535 | 536 | // more than 2-3 parallel searches cause timeouts 537 | res.push( 538 | ...(await Promise.all([attackBlocksReport(1), attackBlocksReport(2)])) 539 | ); 540 | res.push( 541 | ...(await Promise.all([attackBlocksReport(3), attackBlocksReport(4)])) 542 | ); 543 | res.push( 544 | ...(await Promise.all([attackBlocksReport(5), attackBlocksReport(6)])) 545 | ); 546 | res.push( 547 | ...(await Promise.all([attackBlocksReport(7), attackBlocksReport(8)])) 548 | ); 549 | res.push( 550 | ...(await Promise.all([ 551 | attackBlocksReport(9), 552 | attackBlocksReport(10), 553 | ])) 554 | ); 555 | 556 | res = res.map(([pump, dump], i) => { 557 | const getSettled = (r, valIndex) => { 558 | if (r.status !== "fulfilled") { 559 | if (r.reason === "max_target") return r.reason; 560 | if (r.reason.message.includes("Max trade value exceeded")) 561 | return "max_trade"; 562 | throw r.reason; 563 | } 564 | return r.value[valIndex]; 565 | }; 566 | return { 567 | blocks: i + 1, 568 | pump: getSettled(pump, 0), 569 | dump: getSettled(dump, 1), 570 | }; 571 | }); 572 | setReportData(res); 573 | // console.log('allResults: ', JSON.stringify(res, null, 2)); 574 | } catch (e) { 575 | handleError( 576 | e.message.includes("context deadline exceeded") 577 | ? "Provider timeout. Try again..." 578 | : e 579 | ); 580 | setReportOpen(false); 581 | } finally { 582 | console.timeEnd("report"); 583 | setReportLoading(false); 584 | setReportProgress(0); 585 | } 586 | }; 587 | // setTimeout(() => { 588 | // const stats = getLiquidityStats(liquidityProfile, currTick, token, fee, currPrice, ethPrice); 589 | // console.log('stats: ', JSON.stringify(stats, null, 2)); 590 | // setLiquidityStats(stats); 591 | // }) 592 | 593 | console.time("report"); 594 | runReport(); 595 | // setReportLoading(false); 596 | // setReportData(reportJson); 597 | }; 598 | 599 | const onMaxTwapTarget = (direction) => () => { 600 | if (direction === "pump") { 601 | setTargetEthTwap(maxTargetTwapSpot); 602 | setTargetUsdTwap(maxTargetTwapSpot * ethPrice); 603 | } else { 604 | setTargetEthTwap(minTargetTwapSpot); 605 | setTargetUsdTwap(minTargetTwapSpot * ethPrice); 606 | } 607 | }; 608 | 609 | const resetResults = () => { 610 | setTargetPriceImpactValue(null); 611 | setTargetPriceValue(null); 612 | setTargetTwapValue(null); 613 | }; 614 | 615 | const resetMarket = () => { 616 | setCurrPrice(null); 617 | setTrades(null); 618 | setTargetPriceImpactValue(null); 619 | setTargetPriceValue(null); 620 | setTargetTwapValue(null); 621 | setFee(3000); 622 | setLiquidityProfile(null); 623 | setLiquidityChartData(null); 624 | setLiquidityStats(null); 625 | setPoolFees([]); 626 | }; 627 | 628 | const handleToken = (option) => { 629 | if (!option) return; 630 | resetMarket(); 631 | setTokenName(option.name); 632 | }; 633 | 634 | const handleFee = (event) => { 635 | const fees = poolFees; 636 | resetMarket(); 637 | setPoolFees(fees); 638 | setFee(event.target.value); 639 | }; 640 | 641 | const handleEthPrice = (event) => { 642 | setEthPrice(event.target.value); 643 | }; 644 | 645 | const handleTargetPrice = (currency) => (event) => { 646 | if (currency === "eth") { 647 | setTargetEthPrice(event.target.value); 648 | setTargetUsdPrice(event.target.value * ethPrice); 649 | } else { 650 | setTargetUsdPrice(event.target.value); 651 | setTargetEthPrice(event.target.value / ethPrice); 652 | } 653 | }; 654 | 655 | const handleTargetTWAP = (currency) => (event) => { 656 | if (currency === "eth") { 657 | setTargetEthTwap(event.target.value); 658 | setTargetUsdTwap(event.target.value * ethPrice); 659 | } else { 660 | setTargetUsdTwap(event.target.value); 661 | setTargetEthTwap(event.target.value / ethPrice); 662 | } 663 | }; 664 | 665 | const handleWindow = (event) => { 666 | setWindow(event.target.value); 667 | }; 668 | 669 | const handleAttackBlocks = (event) => { 670 | setAttackBlocks(event.target.value); 671 | }; 672 | 673 | const handleDownload = () => { 674 | csvLink.current.link.click(); 675 | }; 676 | 677 | const handleTargetPriceImpact = (event) => { 678 | setTargetPriceImpact(event.target.value); 679 | }; 680 | 681 | const handleReportBorrowFactor = (event) => { 682 | setReportBorrowFactor(event.target.value); 683 | }; 684 | 685 | const handleReportCollateralFactor = (event) => { 686 | setReportCollateralFactor(event.target.value); 687 | }; 688 | 689 | const handleReportClose = () => { 690 | setReportOpen(false); 691 | }; 692 | 693 | const handleErrorClose = () => { 694 | setErrorOpen(false); 695 | }; 696 | 697 | const handleError = (e) => { 698 | if (e.message !== "cancelled") { 699 | setError(e.message || e); 700 | setErrorOpen(true); 701 | } 702 | }; 703 | // todo here 704 | const stringToFixed = (val, precision) => { 705 | const i = val.indexOf("."); 706 | return Number(i === -1 ? val : val.slice(0, i + precision + 1)); 707 | }; 708 | let pumpChartData = 709 | (trades && 710 | trades.pump.map((s) => ({ 711 | ...s, 712 | priceImpact: stringToFixed(s.priceImpact, 3), 713 | }))) || 714 | []; 715 | let dumpChartData = 716 | (trades && 717 | trades.dump.map((s) => ({ 718 | ...s, 719 | priceImpact: stringToFixed(s.priceImpact, 3), 720 | }))) || 721 | []; 722 | 723 | const tokenSelectOptions = tokenList.map((t, i) => ({ 724 | ...t, 725 | label: 726 | tokenList.filter((a) => a.symbol === t.symbol).length > 1 727 | ? `${t.symbol} ${t.name}` 728 | : t.symbol, 729 | })); 730 | const tokenSelectValue = tokenSelectOptions.find( 731 | (o) => o.name === tokenName 732 | ) || { label: "" }; 733 | 734 | const SearchResult = ({ result }) => { 735 | return ( 736 | 737 | 738 | Value: 739 | 740 | 741 | ${result.value.toLocaleString()} 742 | 743 | {result.targetSpot && ( 744 | <> 745 | 746 | Target Spot ETH: 747 | 748 | 749 | {result.targetSpot} 750 | 751 | 752 | Target Spot USD: 753 | 754 | 755 | {(result.targetSpot * ethPrice).toLocaleString()} 756 | 757 | 758 | )} 759 | 760 | Price Impact: 761 | 762 | 763 | {Number(result.priceImpact).toLocaleString()} % 764 | 765 | 766 | Price ETH: 767 | 768 | 769 | {result.price} 770 | 771 | 772 | Price USD: 773 | 774 | 775 | {(result.price * ethPrice).toLocaleString()} 776 | 777 | 778 | Cost USD: 779 | 780 | 781 | $ 782 | {getCostOfAttack(result, currPrice, ethPrice, token).toLocaleString()} 783 | 784 | 785 | ); 786 | }; 787 | const filterOptions = (options, { inputValue }) => { 788 | return matchSorter(options, inputValue, { 789 | keys: ["name", "symbol", "address"], 790 | }); 791 | }; 792 | 793 | return ( 794 | 795 | 796 | 797 | 798 | } 804 | value={tokenSelectValue} 805 | isOptionEqualToValue={(a, b) => a.name === b.name} 806 | onChange={(event, option) => handleToken(option)} 807 | /> 808 | 809 | 810 | 811 | 812 | Fee 813 | 849 | 850 | 851 | 852 | 853 | USD, 861 | }} 862 | /> 863 | 864 | 865 | 866 | 867 | 876 | % 877 | 889 | 890 | 891 | 892 | ), 893 | }} 894 | /> 895 | 896 | 897 | 898 | 899 | 920 | 921 | 922 | ), 923 | }} 924 | /> 925 | 926 | 927 | 928 | 929 | 950 | 951 | 952 | ), 953 | }} 954 | /> 955 | 956 | 957 | 958 | 959 | 967 | 968 | 969 | 970 | 971 | 979 | 980 | 981 | 982 | 983 | 1005 | 1006 | 1007 | ), 1008 | }} 1009 | /> 1010 | 1011 | 1012 | 1013 | 1014 | 1036 | 1037 | 1038 | ), 1039 | }} 1040 | /> 1041 | 1042 | 1043 | 1052 | {trades && trades.pump.length > 0 && ( 1053 | 1063 | pump && 1064 | dump && [ 1065 | pump.value, 1066 | pump.priceImpact, 1067 | pump.price, 1068 | dump.priceImpact, 1069 | dump.price, 1070 | ] 1071 | )} 1072 | target="_blank" 1073 | filename={`${tokenName}_${fee}.csv`} 1074 | ref={csvLink} 1075 | /> 1076 | )} 1077 | 1078 | 1079 | 1080 | 1} 1086 | onChange={handleReportBorrowFactor} 1087 | InputLabelProps={{ shrink: true }} 1088 | /> 1089 | 1090 | 1091 | 1092 | 1093 | 1} 1099 | onChange={handleReportCollateralFactor} 1100 | InputLabelProps={{ shrink: true }} 1101 | /> 1102 | 1103 | 1104 | 1105 | 1112 | 1113 | 1114 | 1115 | {trades ? ( 1116 | <> 1117 | 1118 | 1119 | 1120 | 1121 | 1122 | 1126 | Token 1127 | 1128 | 1137 | Pool 1138 | 1139 | 1140 | Tick: {currTick} 1141 | 1142 | 1143 | Cardinality: {cardinality} 1144 | 1145 | 1146 | 1147 | 1148 | Price USD: {formatPrice(currPrice, token) * ethPrice} 1149 | 1150 | 1151 | Price ETH: {formatPrice(currPrice, token)} 1152 | 1153 | 1154 | 1155 | 1156 | Max TWAP targets USD (given window, attack blocks and tick 1157 | pricing limits) 1158 | 1159 | 1160 | 1164 | Pump: {maxTargetTwapSpot * ethPrice} ( 1165 | {maxTargetTwapSpotPercentage}%) 1166 | 1167 | 1171 | Dump: {minTargetTwapSpot * ethPrice} ( 1172 | {minTargetTwapSpotPercentage}%) 1173 | 1174 | 1175 | 1176 | 1177 | 1178 | 1179 | {targetPriceImpactValue && ( 1180 | 1181 | 1182 | 1183 | 1184 | Target Price Impact 1185 | 1186 | 1187 | 1188 | 1189 | 1190 | 1191 | 1192 | )} 1193 | {targetPriceValue && ( 1194 | 1195 | 1196 | 1197 | 1198 | Target Spot 1199 | 1200 | 1203 | 1204 | 1205 | 1206 | )} 1207 | {targetTwapValue && ( 1208 | 1209 | 1210 | 1211 | 1212 | Target TWAP 1213 | 1214 | 1220 | 1221 | 1222 | 1223 | )} 1224 | 1225 | 1226 | 1231 | 1232 | 1233 | USD VALUE 1234 | PUMP SPOT IMPACT 1235 | DUMP SPOT IMPACT 1236 | 1237 | 1238 | 1239 | {getStandardTradesTable().map( 1240 | (row) => 1241 | row.pump && ( 1242 | 1248 | 1253 | {numberFormatText(row.pump.value)} 1254 | 1255 | 1256 | {row.pump.priceImpact}% 1257 | 1258 | 1259 | {row.dump.priceImpact}% 1260 | 1261 | 1262 | ) 1263 | )} 1264 | 1265 |
1266 |
1267 |
1268 | {/* todo here */} 1269 | {trades && trades.pump.length === 0 && ( 1270 | 1271 | NO LIQUIDITY 1272 | 1273 | )} 1274 |
1275 | {trades && trades.pump.length > 0 && ( 1276 | 1277 | 1291 | 1305 | {/* {liquidityProfile && ( 1306 | 1313 | )} */} 1314 | 1315 | )} 1316 | 1317 | ) : ( 1318 | 1324 | 1325 | 1326 | )} 1327 | 1328 | ERROR 1329 | 1330 | 1331 | {error} 1332 | 1333 | 1334 | 1335 | 1336 | 1337 | 1338 | 1339 | 1345 | 1346 | 1347 | {token.symbol} / WETH {fee / 10000}% 1348 | 1349 | 1350 | 1351 | 1363 | 1364 | 1365 | 1368 | 1369 | 1370 |
1371 | ); 1372 | }; 1373 | -------------------------------------------------------------------------------- /src/components/Main/index.jsx: -------------------------------------------------------------------------------- 1 | export { Main } from './Main'; -------------------------------------------------------------------------------- /src/components/PriceImpactChart/PriceImpactChart.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | LineChart, 4 | Line, 5 | XAxis, 6 | YAxis, 7 | CartesianGrid, 8 | Tooltip, 9 | Legend, 10 | ReferenceLine, 11 | } from "recharts"; 12 | import { DefaultTooltipContent } from "recharts/lib/component/DefaultTooltipContent"; 13 | import { utils } from "ethers"; 14 | 15 | import { WETH_ADDRESS } from "../../utils/constants"; 16 | import { numberFormatText, getCostOfAttack } from "../../utils"; 17 | 18 | export const PriceImpactChart = ({ 19 | width, 20 | height, 21 | stroke, 22 | data, 23 | targetPriceImpact, 24 | targetPrice, 25 | targetTwap, 26 | token, 27 | ethPrice, 28 | currPrice, 29 | }) => { 30 | const CustomTooltip = (props) => { 31 | if (props.payload[0] != null) { 32 | const payload = props.payload[0].payload; 33 | const amountIn = utils.formatUnits( 34 | payload.amountIn, 35 | payload.tokenOut === WETH_ADDRESS ? token.decimals : 18 36 | ); 37 | const amountOut = utils.formatUnits( 38 | payload.amountOut, 39 | payload.tokenOut === WETH_ADDRESS ? 18 : token.decimals 40 | ); 41 | const newPayload = [ 42 | ...props.payload, 43 | { 44 | name: "price ETH", 45 | value: payload.price, 46 | }, 47 | { 48 | name: "price USD", 49 | value: payload.price * ethPrice, 50 | }, 51 | { 52 | name: "amount in", 53 | value: `${amountIn} ${ 54 | payload.tokenOut === WETH_ADDRESS ? token.symbol : "WETH" 55 | }`, 56 | }, 57 | { 58 | name: "amount out", 59 | value: `${amountOut} ${ 60 | payload.tokenOut === WETH_ADDRESS ? "WETH" : token.symbol 61 | }`, 62 | }, 63 | { 64 | name: "cost", 65 | value: 66 | getCostOfAttack( 67 | payload, 68 | currPrice, 69 | ethPrice, 70 | token 71 | ).toLocaleString() + " USD", 72 | }, 73 | ]; 74 | 75 | return ; 76 | } 77 | 78 | // we just render the default 79 | return ; 80 | }; 81 | 82 | return ( 83 | 94 | 95 | { 100 | return numberFormatText(tick); 101 | }} 102 | /> 103 | 104 | v.toLocaleString() + " USD"} 107 | formatter={(value, name) => [ 108 | name === "price impact" ? `${value}%` : value, 109 | name, 110 | ]} 111 | /> 112 | 113 | {targetPriceImpact && ( 114 | 119 | )} 120 | {targetPrice && ( 121 | 126 | )} 127 | {targetTwap && ( 128 | 133 | )} 134 | 141 | 142 | ); 143 | }; 144 | -------------------------------------------------------------------------------- /src/components/PriceImpactChart/index.js: -------------------------------------------------------------------------------- 1 | export { PriceImpactChart } from "./PriceImpactChart"; -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/utils/constants.js: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "ethers"; 2 | import { Decimal } from "decimal.js"; 3 | 4 | export const UNISWAP_QUOTERV2_ADDRESS = 5 | "0x0209c4Dc18B2A1439fD2427E34E7cF3c6B91cFB9"; 6 | export const UNISWAP_FACTORY_ADDRESS = 7 | "0x1F98431c8aD98523631AE4a59f267346ea31F984"; 8 | export const POOL_INIT_CODE_HASH = 9 | "0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54"; 10 | export const EULER_VIEW_ADDRESS = "0x9D2B3052f5A3c156A34FC32cD08E9F5501720ea4"; 11 | export const EULER_CONTRACT_ADDRESS = 12 | "0x27182842E098f60e3D576794A5bFFb0777E025d3"; 13 | export const USDC_ADDRESS = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; 14 | export const WETH_ADDRESS = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"; 15 | export const MAX_TICK_PRICE = Decimal.pow(1.0001, 887272); 16 | export const MIN_TICK_PRICE = Decimal.pow(1.0001, -887272); 17 | export const c1e18 = BigNumber.from(10).pow(18); 18 | export const QUOTER_ABI = [ 19 | "function quoteExactInputSingle(tuple(address tokenIn,address tokenOut,uint256 amountIn,uint24 fee,uint160 sqrtPriceLimitX96) params) public returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)", 20 | ]; 21 | export const UNISWAP_V3_POOL_ABI = [ 22 | "function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked)", 23 | ]; 24 | export const UNISWAP_V3_FACTORY_ABI = [ 25 | "function getPool(address token0, address token1, uint24 fee) public view returns (address)", 26 | ]; 27 | export const TICK_SPACINGS = { 28 | 100: 1, 29 | 500: 10, 30 | 3000: 60, 31 | 10000: 200, 32 | }; 33 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export * from "./utils"; 2 | export * from "./constants"; 3 | export * from "./trades"; 4 | export * from "./liquidityProfile"; -------------------------------------------------------------------------------- /src/utils/liquidityProfile.js: -------------------------------------------------------------------------------- 1 | import { utils } from "ethers"; 2 | import { Decimal } from "decimal.js"; 3 | 4 | import { WETH_ADDRESS, TICK_SPACINGS } from "./constants"; 5 | import { computeUniV3PoolAddress, formatPrice } from "."; 6 | import { 7 | getSqrtRatioAtTick, 8 | getAmount0ForLiquidity, 9 | getAmount1ForLiquidity, 10 | } from "./tickMath"; 11 | 12 | export const getLiquidityProfile = async (token, fee) => { 13 | const lpUrl = process.env.REACT_APP_LIQUIDITY_PROFILE_HTTP; 14 | if (!lpUrl) return; 15 | const pool = computeUniV3PoolAddress(token.address, WETH_ADDRESS, fee); 16 | // console.log('`${lpUrl}?contract_address=${pool}`: ', `${lpUrl}?contract_address=${pool.toLowerCase()}`); 17 | // let p = await axios.get(`${lpUrl}?contract_address=${pool.toLowerCase()}`); 18 | // p = p.data.split('\n').slice(1, p.length - 1); 19 | // p = p.map(tick => { 20 | // const [ iterator, amount, initialised ] = tick.split(','); 21 | // return { iterator, amount, initialised }; 22 | // }); 23 | // return lp; 24 | }; 25 | 26 | export const processLiquidityRange = ( 27 | liquidityProfile, 28 | currTick, 29 | token, 30 | ethPrice, 31 | price, 32 | rangeLeft, 33 | rangeRight 34 | ) => { 35 | price = formatPrice(price, token); 36 | const tickIndex = 37 | liquidityProfile.findIndex((l) => l.iterator > currTick) - 1; 38 | const range = 39 | rangeLeft || rangeRight 40 | ? liquidityProfile.slice(tickIndex - rangeLeft, tickIndex + rangeRight) 41 | : liquidityProfile; 42 | return range.map((l, i, arr) => { 43 | const tokenAmount = 44 | i === arr.length - 1 45 | ? "0" 46 | : l.iterator < currTick 47 | ? utils.formatEther( 48 | getAmount1ForLiquidity( 49 | getSqrtRatioAtTick(l.iterator), 50 | getSqrtRatioAtTick(arr[i + 1].iterator), 51 | new Decimal(l.amount).toFixed() 52 | ) 53 | ) 54 | : utils.formatUnits( 55 | getAmount0ForLiquidity( 56 | getSqrtRatioAtTick(l.iterator), 57 | getSqrtRatioAtTick(arr[i + 1].iterator), 58 | new Decimal(l.amount).toFixed() 59 | ), 60 | token.decimals 61 | ); 62 | 63 | return { 64 | tick: l.iterator, 65 | liquidity: l.amount, 66 | tokenAmount, 67 | usdValue: 68 | l.iterator < currTick 69 | ? tokenAmount * ethPrice 70 | : tokenAmount * ethPrice * price, 71 | symbol: l.iterator < currTick ? "WETH" : token.symbol, 72 | }; 73 | }); 74 | }; 75 | 76 | export const getLiquidityStats = ( 77 | liquidityProfile, 78 | currTick, 79 | token, 80 | fee, 81 | price, 82 | ethPrice 83 | ) => { 84 | const liquidityLevelsUsd = [0, 10, 100, 1000, 100000]; 85 | 86 | let tickLiquidityStats = Object.fromEntries( 87 | liquidityLevelsUsd.map((l) => [ 88 | l, 89 | { 90 | count: 0, 91 | }, 92 | ]) 93 | ); 94 | const fullRange = processLiquidityRange( 95 | liquidityProfile, 96 | currTick, 97 | token, 98 | ethPrice, 99 | price 100 | ); 101 | fullRange.forEach((tick, i) => { 102 | Object.entries(tickLiquidityStats).forEach(([level, stats]) => { 103 | if (level === 0) { 104 | if (utils.parseEther(tick.tokenAmount).gt(0)) { 105 | stats.count += 1; 106 | } 107 | } else { 108 | if (tick.usdValue > level) { 109 | stats.count += 1; 110 | } 111 | } 112 | }); 113 | }); 114 | 115 | Object.entries(tickLiquidityStats).forEach(([level, stats]) => { 116 | tickLiquidityStats[level].percentage = 117 | (stats.count / Math.floor((887272 * 2 + 1) / TICK_SPACINGS[fee])) * 100; 118 | }); 119 | 120 | return tickLiquidityStats; 121 | }; 122 | -------------------------------------------------------------------------------- /src/utils/tickMath.js: -------------------------------------------------------------------------------- 1 | // borrowed from @uniswap/v3-sdk 2 | 3 | import JSBI from "jsbi" 4 | import { constants, BigNumber } from "ethers" 5 | 6 | const Q32 = JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(32)) 7 | export const ZERO = JSBI.BigInt(0) 8 | export const ONE = JSBI.BigInt(1) 9 | 10 | 11 | function mulShift(val, mulBy) { 12 | return JSBI.signedRightShift(JSBI.multiply(val, JSBI.BigInt(mulBy)), JSBI.BigInt(128)) 13 | } 14 | 15 | export const getSqrtRatioAtTick = (tick) => { 16 | const absTick = tick < 0 ? tick * -1 : tick 17 | 18 | let ratio = 19 | (absTick & 0x1) != 0 20 | ? JSBI.BigInt('0xfffcb933bd6fad37aa2d162d1a594001') 21 | : JSBI.BigInt('0x100000000000000000000000000000000') 22 | if ((absTick & 0x2) != 0) ratio = mulShift(ratio, '0xfff97272373d413259a46990580e213a') 23 | if ((absTick & 0x4) != 0) ratio = mulShift(ratio, '0xfff2e50f5f656932ef12357cf3c7fdcc') 24 | if ((absTick & 0x8) != 0) ratio = mulShift(ratio, '0xffe5caca7e10e4e61c3624eaa0941cd0') 25 | if ((absTick & 0x10) != 0) ratio = mulShift(ratio, '0xffcb9843d60f6159c9db58835c926644') 26 | if ((absTick & 0x20) != 0) ratio = mulShift(ratio, '0xff973b41fa98c081472e6896dfb254c0') 27 | if ((absTick & 0x40) != 0) ratio = mulShift(ratio, '0xff2ea16466c96a3843ec78b326b52861') 28 | if ((absTick & 0x80) != 0) ratio = mulShift(ratio, '0xfe5dee046a99a2a811c461f1969c3053') 29 | if ((absTick & 0x100) != 0) ratio = mulShift(ratio, '0xfcbe86c7900a88aedcffc83b479aa3a4') 30 | if ((absTick & 0x200) != 0) ratio = mulShift(ratio, '0xf987a7253ac413176f2b074cf7815e54') 31 | if ((absTick & 0x400) != 0) ratio = mulShift(ratio, '0xf3392b0822b70005940c7a398e4b70f3') 32 | if ((absTick & 0x800) != 0) ratio = mulShift(ratio, '0xe7159475a2c29b7443b29c7fa6e889d9') 33 | if ((absTick & 0x1000) != 0) ratio = mulShift(ratio, '0xd097f3bdfd2022b8845ad8f792aa5825') 34 | if ((absTick & 0x2000) != 0) ratio = mulShift(ratio, '0xa9f746462d870fdf8a65dc1f90e061e5') 35 | if ((absTick & 0x4000) != 0) ratio = mulShift(ratio, '0x70d869a156d2a1b890bb3df62baf32f7') 36 | if ((absTick & 0x8000) != 0) ratio = mulShift(ratio, '0x31be135f97d08fd981231505542fcfa6') 37 | if ((absTick & 0x10000) != 0) ratio = mulShift(ratio, '0x9aa508b5b7a84e1c677de54f3e99bc9') 38 | if ((absTick & 0x20000) != 0) ratio = mulShift(ratio, '0x5d6af8dedb81196699c329225ee604') 39 | if ((absTick & 0x40000) != 0) ratio = mulShift(ratio, '0x2216e584f5fa1ea926041bedfe98') 40 | if ((absTick & 0x80000) != 0) ratio = mulShift(ratio, '0x48a170391f7dc42444e8fa2') 41 | 42 | if (tick > 0) ratio = JSBI.divide(JSBI.BigInt(constants.MaxUint256.toString()), ratio) 43 | 44 | // back to Q96 45 | return JSBI.greaterThan(JSBI.remainder(ratio, Q32), ZERO) 46 | ? JSBI.add(JSBI.divide(ratio, Q32), ONE).toString() 47 | : JSBI.divide(ratio, Q32).toString() 48 | } 49 | 50 | 51 | export const getAmount0ForLiquidity = (sqrtRatioAX96, sqrtRatioBX96, liquidity) => { 52 | sqrtRatioAX96 = BigNumber.from(sqrtRatioAX96); 53 | sqrtRatioBX96 = BigNumber.from(sqrtRatioBX96); 54 | if (sqrtRatioAX96.gt(sqrtRatioBX96)) [sqrtRatioAX96, sqrtRatioBX96] = [sqrtRatioBX96, sqrtRatioAX96]; 55 | 56 | return BigNumber.from(liquidity) 57 | .mul(BigNumber.from(2).pow(96)) 58 | .mul(sqrtRatioBX96.sub(sqrtRatioAX96)) 59 | .div(sqrtRatioBX96) 60 | .div(sqrtRatioAX96); 61 | }; 62 | 63 | export const getAmount1ForLiquidity = (sqrtRatioAX96, sqrtRatioBX96, liquidity) => { 64 | sqrtRatioAX96 = BigNumber.from(sqrtRatioAX96); 65 | sqrtRatioBX96 = BigNumber.from(sqrtRatioBX96); 66 | if (sqrtRatioAX96.gt(sqrtRatioBX96)) [sqrtRatioAX96, sqrtRatioBX96] = [sqrtRatioBX96, sqrtRatioAX96]; 67 | 68 | return BigNumber.from(liquidity) 69 | .mul(sqrtRatioBX96.sub(sqrtRatioAX96)) 70 | .div('0x1000000000000000000000000') 71 | }; 72 | 73 | export const getLiquidityForAmount0 = (sqrtRatioAX96, sqrtRatioBX96, amount0) => { 74 | sqrtRatioAX96 = BigNumber.from(sqrtRatioAX96); 75 | sqrtRatioBX96 = BigNumber.from(sqrtRatioBX96); 76 | if (sqrtRatioAX96.gt(sqrtRatioBX96)) [sqrtRatioAX96, sqrtRatioBX96] = [sqrtRatioBX96, sqrtRatioAX96]; 77 | const intermediate = sqrtRatioAX96.mul(sqrtRatioBX96).div('0x1000000000000000000000000') 78 | return BigNumber.from(amount0) 79 | .mul(intermediate) 80 | .div(sqrtRatioBX96.sub(sqrtRatioAX96)); 81 | 82 | }; 83 | 84 | export const getLiquidityForAmount1 = (sqrtRatioAX96, sqrtRatioBX96, amount1) => { 85 | sqrtRatioAX96 = BigNumber.from(sqrtRatioAX96); 86 | sqrtRatioBX96 = BigNumber.from(sqrtRatioBX96); 87 | if (sqrtRatioAX96.gt(sqrtRatioBX96)) [sqrtRatioAX96, sqrtRatioBX96] = [sqrtRatioBX96, sqrtRatioAX96]; 88 | return BigNumber.from(amount1) 89 | .mul('0x1000000000000000000000000') 90 | .div(sqrtRatioBX96.sub(sqrtRatioAX96)); 91 | }; -------------------------------------------------------------------------------- /src/utils/trades.js: -------------------------------------------------------------------------------- 1 | import { Contract, utils } from "ethers"; 2 | import { Decimal } from "decimal.js"; 3 | import { sortBy } from "lodash"; 4 | 5 | import { 6 | WETH_ADDRESS, 7 | UNISWAP_QUOTERV2_ADDRESS, 8 | QUOTER_ABI, 9 | c1e18, 10 | } from "./constants"; 11 | import { 12 | isInverted, 13 | provider, 14 | sqrtPriceX96ToPrice, 15 | formatPrice, 16 | } from "."; 17 | 18 | 19 | const quoterContract = new Contract( 20 | UNISWAP_QUOTERV2_ADDRESS, 21 | QUOTER_ABI, 22 | provider 23 | ); 24 | 25 | export const getDump = async ( 26 | currPrice, 27 | token, 28 | fee, 29 | ethPrice, 30 | tradeValueInUSD 31 | ) => { 32 | if (token.address.toLowerCase() === WETH_ADDRESS) 33 | return { value: tradeValueInUSD, price: "0", priceImpact: "0" }; 34 | 35 | try { 36 | let inverted = isInverted(token.address); 37 | let quote; 38 | 39 | let amountIn = utils 40 | .parseEther(new Decimal(tradeValueInUSD / ethPrice).toFixed(18)) 41 | .mul(c1e18) 42 | .div(currPrice.eq(0) ? 1 : currPrice); 43 | quote = await quoterContract.callStatic.quoteExactInputSingle({ 44 | tokenIn: token.address, 45 | tokenOut: WETH_ADDRESS, 46 | fee, 47 | amountIn, 48 | sqrtPriceLimitX96: 0, 49 | }); 50 | let after = sqrtPriceX96ToPrice(quote.sqrtPriceX96After, inverted); 51 | const priceImpact = utils.formatEther( 52 | after.sub(currPrice).mul(c1e18).div(currPrice.eq(0) ? 1 : currPrice).mul(100) 53 | ); 54 | 55 | return { 56 | amountIn, 57 | value: tradeValueInUSD, 58 | priceImpact, 59 | sqrtPriceX96After: quote.sqrtPriceX96After.toString(), 60 | price: formatPrice(after, token), 61 | after, 62 | amountOut: quote.amountOut, 63 | tokenOut: WETH_ADDRESS, 64 | gasEstimate: quote.gasEstimate, 65 | }; 66 | } catch (e) { 67 | console.log("e dump: ", token.symbol, e); 68 | throw e; 69 | } 70 | }; 71 | 72 | export const getPump = async ( 73 | currPrice, 74 | token, 75 | fee, 76 | ethPrice, 77 | tradeValueInUSD 78 | ) => { 79 | if (token.address.toLowerCase() === WETH_ADDRESS) 80 | return { value: tradeValueInUSD, price: "0", priceImpact: "0" }; 81 | 82 | try { 83 | let inverted = isInverted(token.address); 84 | let quote; 85 | 86 | let amountIn = utils.parseEther(new Decimal(tradeValueInUSD / ethPrice).toFixed(18)); 87 | quote = await quoterContract.callStatic.quoteExactInputSingle({ 88 | tokenIn: WETH_ADDRESS, 89 | tokenOut: token.address, 90 | fee, 91 | amountIn, 92 | sqrtPriceLimitX96: 0, 93 | }); 94 | 95 | let after = sqrtPriceX96ToPrice(quote.sqrtPriceX96After, inverted); 96 | 97 | const priceImpact = utils.formatEther( 98 | after.sub(currPrice).mul(c1e18).div(currPrice.eq(0) ? 1 : currPrice).mul(100) 99 | ); 100 | 101 | return { 102 | amountIn, 103 | value: tradeValueInUSD, 104 | priceImpact, 105 | sqrtPriceX96After: quote.sqrtPriceX96After.toString(), 106 | price: formatPrice(after, token), 107 | after, 108 | amountOut: quote.amountOut, 109 | tokenOut: token.address, 110 | gasEstimate: quote.gasEstimate, 111 | }; 112 | } catch (e) { 113 | console.log("e pump: ", token.symbol, e); 114 | throw e; 115 | } 116 | }; 117 | 118 | export const getPumpAndDump = async ( 119 | currPrice, 120 | token, 121 | fee, 122 | ethPrice, 123 | tradeValueInUSD 124 | ) => { 125 | const [pump, dump] = await Promise.all([ 126 | getPump(currPrice, token, fee, ethPrice, tradeValueInUSD), 127 | getDump(currPrice, token, fee, ethPrice, tradeValueInUSD), 128 | ]); 129 | return { pump, dump }; 130 | }; 131 | 132 | // TODO only price target 133 | export const searchTrade = ( 134 | currPrice, 135 | currSqrtPriceX96, 136 | token, 137 | fee, 138 | ethPrice, 139 | target, 140 | targetType, 141 | direction 142 | ) => { 143 | let currPriceFormatted = formatPrice(currPrice, token); 144 | 145 | if ( 146 | (targetType === "price" && 147 | ((direction === "pump" && target.lte(currPriceFormatted)) || 148 | (direction === "dump" && target.gte(currPriceFormatted)))) || 149 | (targetType === "sqrtPriceX96After" && 150 | ((direction === "pump" && target.lte(currSqrtPriceX96)) || 151 | (direction === "dump" && target.gte(currSqrtPriceX96)))) 152 | ) { 153 | return []; 154 | } 155 | 156 | let isCancelled = false; 157 | const cancel = () => { 158 | isCancelled = true; 159 | }; 160 | 161 | const exec = async () => { 162 | let high = 1_000_000_000; 163 | let low = 0; 164 | let tolerance = 0.01; 165 | let ranges = 20; 166 | 167 | let allTrades = []; 168 | let best; 169 | 170 | // TODO improve this hack 171 | const inverted = isInverted(token.address); 172 | const adjustedDirection = inverted 173 | ? { pump: "dump", dump: "pump" }[direction] 174 | : direction; 175 | 176 | const getTrade = adjustedDirection === "pump" ? getPump : getDump; 177 | const getNewTicks = (_, i) => low + ((high - low) / ranges) * (i + 1); 178 | const getTickTrade = async (tick, index) => { 179 | const trade = await getTrade(currPrice, token, fee, ethPrice, tick); 180 | allTrades.push(trade); 181 | return { 182 | ...trade, 183 | priceImpact: Math.abs(trade.priceImpact), 184 | value: tick, 185 | index, 186 | }; 187 | }; 188 | 189 | const findBest = (samples) => samples.reduce((accu, s, i) => { 190 | const sampleVal = new Decimal(s[targetType]); 191 | const accuVal = new Decimal(accu[targetType]); 192 | 193 | if ( 194 | sampleVal 195 | .log(10) 196 | .sub(target.log(10)) 197 | .abs() 198 | .lessThan(accuVal.log(10).sub(target.log(10)).abs()) 199 | ) { 200 | // console.log(direction, 'found:', s) 201 | return s; 202 | } 203 | return accu; 204 | }); 205 | 206 | let i = 0; 207 | while (high - low > high * tolerance && high >= 100) { 208 | if (isCancelled) throw new Error("cancelled"); 209 | 210 | let ticks = Array(ranges - 1) 211 | .fill(null) 212 | .map(getNewTicks); 213 | // console.log(direction, 'ticks: ', ticks); 214 | 215 | const samples = await Promise.all(ticks.map(getTickTrade)); 216 | 217 | best = findBest(samples); 218 | // console.log(direction, 'best: ', best); 219 | 220 | // best result is to the far right, increase range 221 | if (i === 0 && best.index === ranges - 2) { 222 | high *= 1_000_000; // 1000 trillions 223 | low = ticks[ranges - 3]; 224 | } else if (i === 1 && best.index === ranges - 2 && high > 1_000_000_000) { 225 | // range was increased already, it's ridiculous to continue 226 | throw new Error("Max trade value exceeded (1000T USD)"); 227 | } else if (best.index === 0) { 228 | // no improvement after the first sample - go down the left 229 | high = ticks[1]; 230 | } else { 231 | // otherwise make sure the range is not flat 232 | for (let j = 0; j < best.index; j++) { 233 | if (samples[j][targetType] !== best[targetType]) { 234 | low = ticks[j]; 235 | } 236 | } 237 | high = ticks[best.index + 1] || high; 238 | } 239 | // console.log(direction, 'low high: ', low, high); 240 | i++; 241 | } 242 | 243 | allTrades = sortBy(allTrades, "value"); 244 | 245 | // take the best trade above target if available 246 | best = 247 | allTrades.find((t) => 248 | targetType === "priceImpact" || direction === "pump" 249 | ? target.lte(Decimal.abs(t[targetType])) 250 | : target.gte(Decimal.abs(t[targetType])) 251 | ) || best; 252 | 253 | // console.log(direction, 'RESULT', best); 254 | return { 255 | best, 256 | // include trades below and a few over 257 | trades: allTrades.filter( 258 | (t) => t.value < Math.max(10_000_000, best.value * 1.2) 259 | ), 260 | }; 261 | }; 262 | return [exec(), cancel]; 263 | }; 264 | 265 | export const binarySearchTradeValues = ( 266 | currPrice, 267 | currSqrtPriceX96, 268 | token, 269 | fee, 270 | ethPrice, 271 | target, 272 | targetType 273 | ) => { 274 | let [execPump, cancelPump] = searchTrade( 275 | currPrice, 276 | currSqrtPriceX96, 277 | token, 278 | fee, 279 | ethPrice, 280 | target, 281 | targetType, 282 | "pump" 283 | ); 284 | let [execDump, cancelDump] = searchTrade( 285 | currPrice, 286 | currSqrtPriceX96, 287 | token, 288 | fee, 289 | ethPrice, 290 | target, 291 | targetType, 292 | "dump" 293 | ); 294 | const cancel = () => { 295 | if (cancelPump) cancelPump(); 296 | if (cancelDump) cancelDump(); 297 | }; 298 | 299 | // todo improve! 300 | if (isInverted(token.address)) [execPump, execDump] = [execDump, execPump]; 301 | return { promise: Promise.all([execPump, execDump]), cancel }; 302 | }; 303 | -------------------------------------------------------------------------------- /src/utils/utils.js: -------------------------------------------------------------------------------- 1 | import { Contract, providers, BigNumber, utils, constants } from "ethers"; 2 | import { Decimal } from "decimal.js"; 3 | import { 4 | UNISWAP_FACTORY_ADDRESS, 5 | POOL_INIT_CODE_HASH, 6 | WETH_ADDRESS, 7 | c1e18, 8 | UNISWAP_V3_POOL_ABI, 9 | UNISWAP_V3_FACTORY_ABI, 10 | EULER_VIEW_ADDRESS, 11 | EULER_CONTRACT_ADDRESS, 12 | MIN_TICK_PRICE, 13 | MAX_TICK_PRICE, 14 | } from "./constants"; 15 | 16 | import eulerViewArtifacts from "../artifacts/EulerGeneralView.json"; 17 | 18 | export const provider = new providers.JsonRpcProvider( 19 | process.env.REACT_APP_ETHEREUM_NETWORK_HTTP 20 | ); 21 | 22 | const factoryContract = new Contract( 23 | UNISWAP_FACTORY_ADDRESS, 24 | UNISWAP_V3_FACTORY_ABI, 25 | provider 26 | ); 27 | const eulerViewContract = new Contract( 28 | EULER_VIEW_ADDRESS, 29 | eulerViewArtifacts.abi, 30 | provider 31 | ); 32 | 33 | Decimal.set({ precision: 50 }); 34 | 35 | export const sqrtPriceX96ToPrice = (a, invert) => { 36 | const scale = new Decimal(2) 37 | .pow(96 * 2) 38 | .div(new Decimal(10).pow(18)) 39 | a = new Decimal(a.toString()) 40 | a = a.mul(a).div(scale) 41 | 42 | if (invert && a.eq(0)) 43 | return BigNumber.from(MAX_TICK_PRICE.toFixed(0)).mul(c1e18) 44 | 45 | if (invert) a = new Decimal(10).pow(18).mul(new Decimal(10).pow(18)).div(a) 46 | 47 | return BigNumber.from(a.toFixed(0)) 48 | } 49 | 50 | // a is decimal 51 | export const priceToSqrtX96Price = (a) => { 52 | a = new Decimal(a); 53 | return a 54 | .mul(Decimal.pow(2, 2 * 96)) 55 | .sqrt() 56 | .floor(); 57 | }; 58 | 59 | export const isInverted = (address) => BigNumber.from(address).gt(WETH_ADDRESS); 60 | 61 | export const getSlot0 = async (token, fee) => { 62 | if (token.address.toLowerCase() === WETH_ADDRESS) return BigNumber.from(1); 63 | try { 64 | const inverted = isInverted(token.address); 65 | const pool = new Contract( 66 | computeUniV3PoolAddress(token.address, WETH_ADDRESS, fee), 67 | UNISWAP_V3_POOL_ABI, 68 | provider 69 | ); 70 | 71 | const res = await pool.slot0(); 72 | return { 73 | ...res, 74 | price: sqrtPriceX96ToPrice(res.sqrtPriceX96, inverted), 75 | }; 76 | } catch (e) { 77 | console.log("current price Error: ", token.symbol, e); 78 | } 79 | }; 80 | 81 | export const getPoolFees = async (address) => { 82 | const [token0, token1] = BigNumber.from(address).gt(WETH_ADDRESS) 83 | ? [WETH_ADDRESS, address] 84 | : [address, WETH_ADDRESS]; 85 | 86 | const pools = await Promise.all( 87 | [100, 500, 3000, 10000].map(async (fee) => { 88 | const pool = await factoryContract.getPool(token0, token1, fee); 89 | if (pool !== constants.AddressZero) return fee; 90 | }) 91 | ); 92 | 93 | return pools.filter(Boolean); 94 | }; 95 | 96 | export const getTwapTargetRatio = ( 97 | targetEthTwap, 98 | token, 99 | currPrice, 100 | window, 101 | attackBlocks 102 | ) => { 103 | if (currPrice.eq(0)) return new Decimal(0); 104 | const inverted = isInverted(token.address); 105 | let target = targetEthTwap; 106 | 107 | if (inverted) { 108 | target = Decimal.div(1, target); 109 | currPrice = Decimal.div(1, currPrice); 110 | } 111 | 112 | target = target 113 | .pow(window) 114 | .div(currPrice.pow(window - attackBlocks)) 115 | .pow(Decimal.div(1, attackBlocks)); 116 | 117 | return priceToSqrtX96Price(target).add(2); 118 | }; 119 | 120 | export const getTwapAfterAttack = ( 121 | manipulatedSpotPrice, 122 | currPrice, 123 | window, 124 | attackBlocks 125 | ) => 126 | manipulatedSpotPrice 127 | .pow(attackBlocks) 128 | .mul(Decimal.pow(currPrice, window - attackBlocks)) 129 | .pow(Decimal.div(1, window)); 130 | 131 | export const numberFormatText = (num, noAbbrev = false) => { 132 | if (Number(num) === 0) { 133 | return 0; 134 | } else if (Number.parseFloat(num) < 1) { 135 | return Number.parseFloat(num).toFixed(6); 136 | } else if (Number.parseFloat(num) < 1) { 137 | return Number.parseFloat(num).toPrecision(6); 138 | } else if (num < 1000 || noAbbrev) { 139 | return Number.parseFloat(num).toFixed(2); 140 | } else { 141 | const si = [ 142 | { value: 1, symbol: "" }, 143 | { value: 1e3, symbol: "k" }, 144 | { value: 1e6, symbol: "M" }, 145 | { value: 1e9, symbol: "B" }, 146 | ]; 147 | const rx = /\.0+$|(\.[0-9]*[1-9])0+$/; 148 | 149 | let i; 150 | for (i = si.length - 1; i > 0; i--) { 151 | if (num >= si[i].value) { 152 | break; 153 | } 154 | } 155 | 156 | return (num / si[i].value).toFixed(2).replace(rx, "$1") + si[i].symbol; 157 | } 158 | }; 159 | 160 | export const formatPrice = (price, token) => { 161 | return utils.formatEther( 162 | BigNumber.from(price).div(BigNumber.from(10).pow(18 - token.decimals)) 163 | ); 164 | }; 165 | 166 | export const computeUniV3PoolAddress = (tokenA, tokenB, fee) => { 167 | const [token0, token1] = BigNumber.from(tokenA).lt(tokenB) 168 | ? [tokenA, tokenB] 169 | : [tokenB, tokenA]; 170 | 171 | return utils.getCreate2Address( 172 | UNISWAP_FACTORY_ADDRESS, 173 | utils.solidityKeccak256( 174 | ["bytes"], 175 | [ 176 | utils.defaultAbiCoder.encode( 177 | ["address", "address", "uint24"], 178 | [token0, token1, fee] 179 | ), 180 | ] 181 | ), 182 | POOL_INIT_CODE_HASH 183 | ); 184 | }; 185 | 186 | export const getMarketConfig = async (underlyingAddress) => { 187 | const res = await eulerViewContract.callStatic.doQuery({ 188 | eulerContract: EULER_CONTRACT_ADDRESS, 189 | account: constants.AddressZero, 190 | markets: [underlyingAddress], 191 | }); 192 | 193 | if (res.markets[0].config.eTokenAddress === constants.AddressZero) 194 | return null; 195 | 196 | const factorScale = 4e9; 197 | return { 198 | borrowFactor: res.markets[0].config.borrowFactor / factorScale, 199 | collateralFactor: res.markets[0].config.collateralFactor / factorScale, 200 | twapWindowSeconds: res.markets[0].config.twapWindow, 201 | }; 202 | }; 203 | 204 | export const getMinMaxTargetTwapSpot = ( 205 | currPrice, 206 | attackBlocks, 207 | window, 208 | token 209 | ) => { 210 | const p = utils.formatEther(currPrice); 211 | 212 | let maxTargetTwapSpot = getTwapAfterAttack( 213 | MAX_TICK_PRICE, 214 | p, 215 | window, 216 | attackBlocks 217 | ); 218 | 219 | let minTargetTwapSpot = getTwapAfterAttack( 220 | MIN_TICK_PRICE, 221 | p, 222 | window, 223 | attackBlocks 224 | ); 225 | 226 | let minTargetTwapSpotPercentage = Decimal.sub(minTargetTwapSpot, p) 227 | .div(p) 228 | .mul(100) 229 | .round() 230 | .toFixed(0); 231 | let maxTargetTwapSpotPercentage = Decimal.sub(maxTargetTwapSpot, p) 232 | .div(p) 233 | .mul(100) 234 | .round() 235 | .toFixed(0); 236 | 237 | maxTargetTwapSpot = maxTargetTwapSpot.div( 238 | Decimal.pow(10, 18 - token.decimals) 239 | ); 240 | minTargetTwapSpot = minTargetTwapSpot.div( 241 | Decimal.pow(10, 18 - token.decimals) 242 | ); 243 | 244 | return { 245 | maxTargetTwapSpot, 246 | minTargetTwapSpot, 247 | maxTargetTwapSpotPercentage, 248 | minTargetTwapSpotPercentage, 249 | }; 250 | }; 251 | 252 | export const getCostOfAttack = (trade, currPrice, ethPrice, token) => { 253 | return trade.tokenOut === WETH_ADDRESS 254 | ? trade.value - utils.formatEther(trade.amountOut) * ethPrice 255 | : trade.value - 256 | utils.formatUnits(trade.amountOut, token.decimals) * 257 | formatPrice(currPrice, token) * 258 | ethPrice; 259 | }; 260 | --------------------------------------------------------------------------------