├── .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 | }
1048 | onClick={handleDownload}
1049 | >
1050 | Download csv
1051 |
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 |
1338 |
1339 |
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 |
--------------------------------------------------------------------------------