bridge_ui: stats page overhaul, added custody-addresses page
This commit is contained in:
parent
76ade1b305
commit
d4a4f8aab5
|
@ -31,6 +31,7 @@
|
|||
"clsx": "^1.1.1",
|
||||
"ethers": "^5.4.1",
|
||||
"js-base64": "^3.6.1",
|
||||
"luxon": "^2.3.1",
|
||||
"notistack": "^1.0.10",
|
||||
"numeral": "^2.0.6",
|
||||
"react": "^17.0.2",
|
||||
|
@ -39,11 +40,13 @@
|
|||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "5.0.0",
|
||||
"react-table": "^7.7.0",
|
||||
"recharts": "^2.1.9",
|
||||
"redux": "^3.7.2",
|
||||
"use-debounce": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@truffle/hdwallet-provider": "^1.4.1",
|
||||
"@types/luxon": "^2.3.1",
|
||||
"@types/node": "^16.6.1",
|
||||
"@types/numeral": "^2.0.2",
|
||||
"@types/react-router-dom": "^5.1.8",
|
||||
|
@ -8474,6 +8477,45 @@
|
|||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/d3-color": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-2.0.3.tgz",
|
||||
"integrity": "sha512-+0EtEjBfKEDtH9Rk3u3kLOUXM5F+iZK+WvASPb0MhIZl8J8NUvGeZRwKCXl+P3HkYx5TdU4YtcibpqHkSR9n7w=="
|
||||
},
|
||||
"node_modules/@types/d3-interpolate": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-2.0.2.tgz",
|
||||
"integrity": "sha512-lElyqlUfIPyWG/cD475vl6msPL4aMU7eJvx1//Q177L8mdXoVPFl1djIESF2FKnc0NyaHvQlJpWwKJYwAhUoCw==",
|
||||
"dependencies": {
|
||||
"@types/d3-color": "^2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-path": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-2.0.2.tgz",
|
||||
"integrity": "sha512-3YHpvDw9LzONaJzejXLOwZ3LqwwkoXb9LI2YN7Hbd6pkGo5nIlJ09ul4bQhBN4hQZJKmUpX8HkVqbzgUKY48cg=="
|
||||
},
|
||||
"node_modules/@types/d3-scale": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.3.2.tgz",
|
||||
"integrity": "sha512-gGqr7x1ost9px3FvIfUMi5XA/F/yAf4UkUDtdQhpH92XCT0Oa7zkkRzY61gPVJq+DxpHn/btouw5ohWkbBsCzQ==",
|
||||
"dependencies": {
|
||||
"@types/d3-time": "^2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-shape": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-2.1.3.tgz",
|
||||
"integrity": "sha512-HAhCel3wP93kh4/rq+7atLdybcESZ5bRHDEZUojClyZWsRuEMo3A52NGYJSh48SxfxEU6RZIVbZL2YFZ2OAlzQ==",
|
||||
"dependencies": {
|
||||
"@types/d3-path": "^2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-time": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.1.1.tgz",
|
||||
"integrity": "sha512-9MVYlmIgmRR31C5b4FVSWtuMmBHh2mOWQYfl7XAYOa8dsnb7iEmUmRSWSFgXFtkjxO65d7hTUHQC+RhR/9IWFg=="
|
||||
},
|
||||
"node_modules/@types/ed2curve": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/ed2curve/-/ed2curve-0.2.2.tgz",
|
||||
|
@ -8670,6 +8712,12 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz",
|
||||
"integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w=="
|
||||
},
|
||||
"node_modules/@types/luxon": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-2.3.1.tgz",
|
||||
"integrity": "sha512-nAPUltOT28fal2eDZz8yyzNhBjHw1NEymFBP7Q9iCShqpflWPybxHbD7pw/46jQmT+HXOy1QN5hNTms8MOTlOQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/mime": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
||||
|
@ -8793,6 +8841,11 @@
|
|||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz",
|
||||
"integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw=="
|
||||
},
|
||||
"node_modules/@types/resize-observer-browser": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/resize-observer-browser/-/resize-observer-browser-0.1.7.tgz",
|
||||
"integrity": "sha512-G9eN0Sn0ii9PWQ3Vl72jDPgeJwRWhv2Qk/nQkJuWmRmOB4HX3/BhD5SE1dZs/hzPZL/WKnvF0RHdTSG54QJFyg=="
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
|
||||
|
@ -13120,6 +13173,11 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/classnames": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
|
||||
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
|
||||
},
|
||||
"node_modules/clean-css": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.2.4.tgz",
|
||||
|
@ -14376,6 +14434,11 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/css-unit-converter": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz",
|
||||
"integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA=="
|
||||
},
|
||||
"node_modules/css-vendor": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz",
|
||||
|
@ -14574,6 +14637,73 @@
|
|||
"type": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-array": {
|
||||
"version": "2.12.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
|
||||
"integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
|
||||
"dependencies": {
|
||||
"internmap": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz",
|
||||
"integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ=="
|
||||
},
|
||||
"node_modules/d3-format": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz",
|
||||
"integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA=="
|
||||
},
|
||||
"node_modules/d3-interpolate": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz",
|
||||
"integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==",
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 2"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-path": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz",
|
||||
"integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA=="
|
||||
},
|
||||
"node_modules/d3-scale": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz",
|
||||
"integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==",
|
||||
"dependencies": {
|
||||
"d3-array": "^2.3.0",
|
||||
"d3-format": "1 - 2",
|
||||
"d3-interpolate": "1.2.0 - 2",
|
||||
"d3-time": "^2.1.1",
|
||||
"d3-time-format": "2 - 3"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-shape": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-2.1.0.tgz",
|
||||
"integrity": "sha512-PnjUqfM2PpskbSLTJvAzp2Wv4CZsnAgTfcVRTwW03QR3MkXF8Uo7B1y/lWkAsmbKwuecto++4NlsYcvYpXpTHA==",
|
||||
"dependencies": {
|
||||
"d3-path": "1 - 2"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-time": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz",
|
||||
"integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==",
|
||||
"dependencies": {
|
||||
"d3-array": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-time-format": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz",
|
||||
"integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==",
|
||||
"dependencies": {
|
||||
"d3-time": "1 - 2"
|
||||
}
|
||||
},
|
||||
"node_modules/damerau-levenshtein": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
||||
|
@ -14679,6 +14809,11 @@
|
|||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz",
|
||||
"integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ=="
|
||||
},
|
||||
"node_modules/decimal.js-light": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
||||
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
|
||||
},
|
||||
"node_modules/decode-uri-component": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||
|
@ -18588,6 +18723,11 @@
|
|||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"node_modules/fast-equals": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-2.0.4.tgz",
|
||||
"integrity": "sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w=="
|
||||
},
|
||||
"node_modules/fast-fifo": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.0.0.tgz",
|
||||
|
@ -22627,6 +22767,11 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/internmap": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
|
||||
"integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="
|
||||
},
|
||||
"node_modules/interpret": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
|
||||
|
@ -28256,6 +28401,14 @@
|
|||
"integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/luxon": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-2.3.1.tgz",
|
||||
"integrity": "sha512-I8vnjOmhXsMSlNMZlMkSOvgrxKJl0uOsEzdGgGNZuZPaS9KlefpE9KV95QFftlJSC+1UyCC9/I69R02cz/zcCA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.25.7",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
|
||||
|
@ -34818,6 +34971,11 @@
|
|||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"node_modules/react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "7.2.5",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.5.tgz",
|
||||
|
@ -34855,6 +35013,20 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-resize-detector": {
|
||||
"version": "6.7.8",
|
||||
"resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-6.7.8.tgz",
|
||||
"integrity": "sha512-0FaEcUBAbn+pq3PT5a9hHRebUfuS1SRLGLpIw8LydU7zX429I6XJgKerKAMPsJH0qWAl6o5bVKNqFJqr6tGPYw==",
|
||||
"dependencies": {
|
||||
"@types/resize-observer-browser": "^0.1.6",
|
||||
"lodash": "^4.17.21",
|
||||
"resize-observer-polyfill": "^1.5.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0 || ^17.0.0",
|
||||
"react-dom": "^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.1.tgz",
|
||||
|
@ -36133,6 +36305,44 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/react-smooth": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.0.tgz",
|
||||
"integrity": "sha512-wK4dBBR6P21otowgMT9toZk+GngMplGS1O5gk+2WSiHEXIrQgDvhR5IIlT74Vtu//qpTcipkgo21dD7a7AUNxw==",
|
||||
"dependencies": {
|
||||
"fast-equals": "^2.0.0",
|
||||
"raf": "^3.4.0",
|
||||
"react-transition-group": "2.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prop-types": "^15.6.0",
|
||||
"react": "^15.0.0 || ^16.0.0 || ^17.0.0",
|
||||
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-smooth/node_modules/dom-helpers": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
|
||||
"integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/react-smooth/node_modules/react-transition-group": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
|
||||
"integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
|
||||
"dependencies": {
|
||||
"dom-helpers": "^3.4.0",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-lifecycles-compat": "^3.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=15.0.0",
|
||||
"react-dom": ">=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-table": {
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/react-table/-/react-table-7.7.0.tgz",
|
||||
|
@ -36194,6 +36404,47 @@
|
|||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/recharts": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.1.9.tgz",
|
||||
"integrity": "sha512-VozH5uznUvGqD7n224FGj7cmMAenlS0HPCs+7r2HeeHiQK6un6z0CTZfWVAB860xbcr4m+BN/EGMPZmYWd34Rg==",
|
||||
"dependencies": {
|
||||
"@types/d3-interpolate": "^2.0.0",
|
||||
"@types/d3-scale": "^3.0.0",
|
||||
"@types/d3-shape": "^2.0.0",
|
||||
"classnames": "^2.2.5",
|
||||
"d3-interpolate": "^2.0.0",
|
||||
"d3-scale": "^3.0.0",
|
||||
"d3-shape": "^2.0.0",
|
||||
"eventemitter3": "^4.0.1",
|
||||
"lodash": "^4.17.19",
|
||||
"react-is": "^16.10.2",
|
||||
"react-resize-detector": "^6.6.3",
|
||||
"react-smooth": "^2.0.0",
|
||||
"recharts-scale": "^0.4.4",
|
||||
"reduce-css-calc": "^2.1.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0 || ^17.0.0",
|
||||
"react-dom": "^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/recharts-scale": {
|
||||
"version": "0.4.5",
|
||||
"resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
|
||||
"integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
|
||||
"dependencies": {
|
||||
"decimal.js-light": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/recharts/node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/recursive-readdir": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz",
|
||||
|
@ -36205,6 +36456,20 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/reduce-css-calc": {
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz",
|
||||
"integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==",
|
||||
"dependencies": {
|
||||
"css-unit-converter": "^1.1.1",
|
||||
"postcss-value-parser": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/reduce-css-calc/node_modules/postcss-value-parser": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
|
||||
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ=="
|
||||
},
|
||||
"node_modules/redux": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
|
||||
|
@ -36782,6 +37047,11 @@
|
|||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/resize-observer-polyfill": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
|
||||
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
|
||||
|
@ -51036,6 +51306,45 @@
|
|||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@types/d3-color": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-2.0.3.tgz",
|
||||
"integrity": "sha512-+0EtEjBfKEDtH9Rk3u3kLOUXM5F+iZK+WvASPb0MhIZl8J8NUvGeZRwKCXl+P3HkYx5TdU4YtcibpqHkSR9n7w=="
|
||||
},
|
||||
"@types/d3-interpolate": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-2.0.2.tgz",
|
||||
"integrity": "sha512-lElyqlUfIPyWG/cD475vl6msPL4aMU7eJvx1//Q177L8mdXoVPFl1djIESF2FKnc0NyaHvQlJpWwKJYwAhUoCw==",
|
||||
"requires": {
|
||||
"@types/d3-color": "^2"
|
||||
}
|
||||
},
|
||||
"@types/d3-path": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-2.0.2.tgz",
|
||||
"integrity": "sha512-3YHpvDw9LzONaJzejXLOwZ3LqwwkoXb9LI2YN7Hbd6pkGo5nIlJ09ul4bQhBN4hQZJKmUpX8HkVqbzgUKY48cg=="
|
||||
},
|
||||
"@types/d3-scale": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-3.3.2.tgz",
|
||||
"integrity": "sha512-gGqr7x1ost9px3FvIfUMi5XA/F/yAf4UkUDtdQhpH92XCT0Oa7zkkRzY61gPVJq+DxpHn/btouw5ohWkbBsCzQ==",
|
||||
"requires": {
|
||||
"@types/d3-time": "^2"
|
||||
}
|
||||
},
|
||||
"@types/d3-shape": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-2.1.3.tgz",
|
||||
"integrity": "sha512-HAhCel3wP93kh4/rq+7atLdybcESZ5bRHDEZUojClyZWsRuEMo3A52NGYJSh48SxfxEU6RZIVbZL2YFZ2OAlzQ==",
|
||||
"requires": {
|
||||
"@types/d3-path": "^2"
|
||||
}
|
||||
},
|
||||
"@types/d3-time": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-2.1.1.tgz",
|
||||
"integrity": "sha512-9MVYlmIgmRR31C5b4FVSWtuMmBHh2mOWQYfl7XAYOa8dsnb7iEmUmRSWSFgXFtkjxO65d7hTUHQC+RhR/9IWFg=="
|
||||
},
|
||||
"@types/ed2curve": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/ed2curve/-/ed2curve-0.2.2.tgz",
|
||||
|
@ -51232,6 +51541,12 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz",
|
||||
"integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w=="
|
||||
},
|
||||
"@types/luxon": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-2.3.1.tgz",
|
||||
"integrity": "sha512-nAPUltOT28fal2eDZz8yyzNhBjHw1NEymFBP7Q9iCShqpflWPybxHbD7pw/46jQmT+HXOy1QN5hNTms8MOTlOQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/mime": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
||||
|
@ -51359,6 +51674,11 @@
|
|||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/resize-observer-browser": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/resize-observer-browser/-/resize-observer-browser-0.1.7.tgz",
|
||||
"integrity": "sha512-G9eN0Sn0ii9PWQ3Vl72jDPgeJwRWhv2Qk/nQkJuWmRmOB4HX3/BhD5SE1dZs/hzPZL/WKnvF0RHdTSG54QJFyg=="
|
||||
},
|
||||
"@types/resolve": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
|
||||
|
@ -54895,6 +55215,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"classnames": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
|
||||
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
|
||||
},
|
||||
"clean-css": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.2.4.tgz",
|
||||
|
@ -55890,6 +56215,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"css-unit-converter": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz",
|
||||
"integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA=="
|
||||
},
|
||||
"css-vendor": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz",
|
||||
|
@ -56037,6 +56367,73 @@
|
|||
"type": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"d3-array": {
|
||||
"version": "2.12.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
|
||||
"integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
|
||||
"requires": {
|
||||
"internmap": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"d3-color": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz",
|
||||
"integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ=="
|
||||
},
|
||||
"d3-format": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz",
|
||||
"integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA=="
|
||||
},
|
||||
"d3-interpolate": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz",
|
||||
"integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==",
|
||||
"requires": {
|
||||
"d3-color": "1 - 2"
|
||||
}
|
||||
},
|
||||
"d3-path": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz",
|
||||
"integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA=="
|
||||
},
|
||||
"d3-scale": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz",
|
||||
"integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==",
|
||||
"requires": {
|
||||
"d3-array": "^2.3.0",
|
||||
"d3-format": "1 - 2",
|
||||
"d3-interpolate": "1.2.0 - 2",
|
||||
"d3-time": "^2.1.1",
|
||||
"d3-time-format": "2 - 3"
|
||||
}
|
||||
},
|
||||
"d3-shape": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-2.1.0.tgz",
|
||||
"integrity": "sha512-PnjUqfM2PpskbSLTJvAzp2Wv4CZsnAgTfcVRTwW03QR3MkXF8Uo7B1y/lWkAsmbKwuecto++4NlsYcvYpXpTHA==",
|
||||
"requires": {
|
||||
"d3-path": "1 - 2"
|
||||
}
|
||||
},
|
||||
"d3-time": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz",
|
||||
"integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==",
|
||||
"requires": {
|
||||
"d3-array": "2"
|
||||
}
|
||||
},
|
||||
"d3-time-format": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz",
|
||||
"integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==",
|
||||
"requires": {
|
||||
"d3-time": "1 - 2"
|
||||
}
|
||||
},
|
||||
"damerau-levenshtein": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
||||
|
@ -56124,6 +56521,11 @@
|
|||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz",
|
||||
"integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ=="
|
||||
},
|
||||
"decimal.js-light": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
||||
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
|
||||
},
|
||||
"decode-uri-component": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||
|
@ -59371,6 +59773,11 @@
|
|||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"fast-equals": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-2.0.4.tgz",
|
||||
"integrity": "sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w=="
|
||||
},
|
||||
"fast-fifo": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.0.0.tgz",
|
||||
|
@ -62587,6 +62994,11 @@
|
|||
"side-channel": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"internmap": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
|
||||
"integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="
|
||||
},
|
||||
"interpret": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
|
||||
|
@ -67046,6 +67458,11 @@
|
|||
"integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=",
|
||||
"dev": true
|
||||
},
|
||||
"luxon": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-2.3.1.tgz",
|
||||
"integrity": "sha512-I8vnjOmhXsMSlNMZlMkSOvgrxKJl0uOsEzdGgGNZuZPaS9KlefpE9KV95QFftlJSC+1UyCC9/I69R02cz/zcCA=="
|
||||
},
|
||||
"magic-string": {
|
||||
"version": "0.25.7",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
|
||||
|
@ -72241,6 +72658,11 @@
|
|||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||
},
|
||||
"react-redux": {
|
||||
"version": "7.2.5",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.5.tgz",
|
||||
|
@ -72266,6 +72688,16 @@
|
|||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
|
||||
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
|
||||
},
|
||||
"react-resize-detector": {
|
||||
"version": "6.7.8",
|
||||
"resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-6.7.8.tgz",
|
||||
"integrity": "sha512-0FaEcUBAbn+pq3PT5a9hHRebUfuS1SRLGLpIw8LydU7zX429I6XJgKerKAMPsJH0qWAl6o5bVKNqFJqr6tGPYw==",
|
||||
"requires": {
|
||||
"@types/resize-observer-browser": "^0.1.6",
|
||||
"lodash": "^4.17.21",
|
||||
"resize-observer-polyfill": "^1.5.1"
|
||||
}
|
||||
},
|
||||
"react-router": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.1.tgz",
|
||||
|
@ -73235,6 +73667,37 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"react-smooth": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.0.tgz",
|
||||
"integrity": "sha512-wK4dBBR6P21otowgMT9toZk+GngMplGS1O5gk+2WSiHEXIrQgDvhR5IIlT74Vtu//qpTcipkgo21dD7a7AUNxw==",
|
||||
"requires": {
|
||||
"fast-equals": "^2.0.0",
|
||||
"raf": "^3.4.0",
|
||||
"react-transition-group": "2.9.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"dom-helpers": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
|
||||
"integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2"
|
||||
}
|
||||
},
|
||||
"react-transition-group": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
|
||||
"integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
|
||||
"requires": {
|
||||
"dom-helpers": "^3.4.0",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-lifecycles-compat": "^3.0.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-table": {
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/react-table/-/react-table-7.7.0.tgz",
|
||||
|
@ -73280,6 +73743,42 @@
|
|||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"recharts": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.1.9.tgz",
|
||||
"integrity": "sha512-VozH5uznUvGqD7n224FGj7cmMAenlS0HPCs+7r2HeeHiQK6un6z0CTZfWVAB860xbcr4m+BN/EGMPZmYWd34Rg==",
|
||||
"requires": {
|
||||
"@types/d3-interpolate": "^2.0.0",
|
||||
"@types/d3-scale": "^3.0.0",
|
||||
"@types/d3-shape": "^2.0.0",
|
||||
"classnames": "^2.2.5",
|
||||
"d3-interpolate": "^2.0.0",
|
||||
"d3-scale": "^3.0.0",
|
||||
"d3-shape": "^2.0.0",
|
||||
"eventemitter3": "^4.0.1",
|
||||
"lodash": "^4.17.19",
|
||||
"react-is": "^16.10.2",
|
||||
"react-resize-detector": "^6.6.3",
|
||||
"react-smooth": "^2.0.0",
|
||||
"recharts-scale": "^0.4.4",
|
||||
"reduce-css-calc": "^2.1.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"recharts-scale": {
|
||||
"version": "0.4.5",
|
||||
"resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
|
||||
"integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
|
||||
"requires": {
|
||||
"decimal.js-light": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"recursive-readdir": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz",
|
||||
|
@ -73288,6 +73787,22 @@
|
|||
"minimatch": "3.0.4"
|
||||
}
|
||||
},
|
||||
"reduce-css-calc": {
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz",
|
||||
"integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==",
|
||||
"requires": {
|
||||
"css-unit-converter": "^1.1.1",
|
||||
"postcss-value-parser": "^3.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"postcss-value-parser": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
|
||||
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"redux": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
|
||||
|
@ -73772,6 +74287,11 @@
|
|||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"resize-observer-polyfill": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
|
||||
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.22.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
"clsx": "^1.1.1",
|
||||
"ethers": "^5.4.1",
|
||||
"js-base64": "^3.6.1",
|
||||
"luxon": "^2.3.1",
|
||||
"notistack": "^1.0.10",
|
||||
"numeral": "^2.0.6",
|
||||
"react": "^17.0.2",
|
||||
|
@ -34,6 +35,7 @@
|
|||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "5.0.0",
|
||||
"react-table": "^7.7.0",
|
||||
"recharts": "^2.1.9",
|
||||
"redux": "^3.7.2",
|
||||
"use-debounce": "^7.0.0"
|
||||
},
|
||||
|
@ -63,6 +65,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@truffle/hdwallet-provider": "^1.4.1",
|
||||
"@types/luxon": "^2.3.1",
|
||||
"@types/node": "^16.6.1",
|
||||
"@types/numeral": "^2.0.2",
|
||||
"@types/react-router-dom": "^5.1.8",
|
||||
|
|
|
@ -36,6 +36,7 @@ import NFT from "./components/NFT";
|
|||
import NFTOriginVerifier from "./components/NFTOriginVerifier";
|
||||
import Recovery from "./components/Recovery";
|
||||
import Stats from "./components/Stats";
|
||||
import CustodyAddresses from "./components/Stats/CustodyAddresses";
|
||||
import TokenOriginVerifier from "./components/TokenOriginVerifier";
|
||||
import Transfer from "./components/Transfer";
|
||||
import UnwrapNative from "./components/UnwrapNative";
|
||||
|
@ -85,19 +86,13 @@ const useStyles = makeStyles((theme) => ({
|
|||
position: "relative",
|
||||
overflow: "hidden",
|
||||
},
|
||||
content: {
|
||||
margin: theme.spacing(2, 0),
|
||||
[theme.breakpoints.up("md")]: {
|
||||
margin: theme.spacing(4, 0),
|
||||
},
|
||||
},
|
||||
headerImage: {
|
||||
position: "absolute",
|
||||
zIndex: -1,
|
||||
top: 0,
|
||||
background: `url(${Header})`,
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundPosition: "top -500px center",
|
||||
backgroundPosition: "top -750px center",
|
||||
backgroundSize: "2070px 1155px",
|
||||
width: "100%",
|
||||
height: 1155,
|
||||
|
@ -291,90 +286,91 @@ function App() {
|
|||
</Typography>
|
||||
</AppBar>
|
||||
) : null}
|
||||
<div className={classes.content}>
|
||||
<div className={classes.headerImage} />
|
||||
{["/transfer", "/nft", "/redeem"].includes(pathname) ? (
|
||||
<Container maxWidth="md" style={{ paddingBottom: 24 }}>
|
||||
<HeaderText
|
||||
white
|
||||
subtitle={
|
||||
<>
|
||||
<Typography>
|
||||
Portal is a bridge that offers unlimited transfers across
|
||||
chains for tokens and NFTs wrapped by Wormhole.
|
||||
</Typography>
|
||||
<Typography>
|
||||
Unlike many other bridges, you avoid double wrapping and
|
||||
never have to retrace your steps.
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
>
|
||||
Token Bridge
|
||||
</HeaderText>
|
||||
<Tabs
|
||||
value={pathname}
|
||||
variant="fullWidth"
|
||||
onChange={handleTabChange}
|
||||
indicatorColor="primary"
|
||||
>
|
||||
<Tab label="Tokens" value="/transfer" />
|
||||
<Tab label="NFTs" value="/nft" />
|
||||
<Tab label="Redeem" value="/redeem" to="/redeem" />
|
||||
</Tabs>
|
||||
</Container>
|
||||
) : null}
|
||||
<Switch>
|
||||
<Route exact path="/transfer">
|
||||
<Transfer />
|
||||
</Route>
|
||||
<Route exact path="/nft">
|
||||
<NFT />
|
||||
</Route>
|
||||
<Route exact path="/redeem">
|
||||
<Recovery />
|
||||
</Route>
|
||||
<Route exact path="/nft-origin-verifier">
|
||||
<NFTOriginVerifier />
|
||||
</Route>
|
||||
<Route exact path="/token-origin-verifier">
|
||||
<TokenOriginVerifier />
|
||||
</Route>
|
||||
<Route exact path="/register">
|
||||
<Attest />
|
||||
</Route>
|
||||
<Route exact path="/migrate/Solana/:legacyAsset/:fromTokenAccount">
|
||||
<Migration chainId={CHAIN_ID_SOLANA} />
|
||||
</Route>
|
||||
<Route exact path="/migrate/Ethereum/:legacyAsset/">
|
||||
<Migration chainId={CHAIN_ID_ETH} />
|
||||
</Route>
|
||||
<Route exact path="/migrate/BinanceSmartChain/:legacyAsset/">
|
||||
<Migration chainId={CHAIN_ID_BSC} />
|
||||
</Route>
|
||||
<Route exact path="/migrate/Ethereum/">
|
||||
<EvmQuickMigrate chainId={CHAIN_ID_ETH} />
|
||||
</Route>
|
||||
<Route exact path="/migrate/BinanceSmartChain/">
|
||||
<EvmQuickMigrate chainId={CHAIN_ID_BSC} />
|
||||
</Route>
|
||||
<Route exact path="/migrate/Solana/">
|
||||
<SolanaQuickMigrate />
|
||||
</Route>
|
||||
<Route exact path="/stats">
|
||||
<Stats />
|
||||
</Route>
|
||||
<Route exact path="/withdraw-tokens-terra">
|
||||
<WithdrawTokensTerra />
|
||||
</Route>
|
||||
<Route exact path="/unwrap-native">
|
||||
<UnwrapNative />
|
||||
</Route>
|
||||
<Route>
|
||||
<Redirect to="/transfer" />
|
||||
</Route>
|
||||
</Switch>
|
||||
</div>
|
||||
<div className={classes.headerImage} />
|
||||
{["/transfer", "/nft", "/redeem"].includes(pathname) ? (
|
||||
<Container maxWidth="md" style={{ paddingBottom: 24 }}>
|
||||
<HeaderText
|
||||
white
|
||||
subtitle={
|
||||
<>
|
||||
<Typography>
|
||||
Portal is a bridge that offers unlimited transfers across
|
||||
chains for tokens and NFTs wrapped by Wormhole.
|
||||
</Typography>
|
||||
<Typography>
|
||||
Unlike many other bridges, you avoid double wrapping and never
|
||||
have to retrace your steps.
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
>
|
||||
Token Bridge
|
||||
</HeaderText>
|
||||
<Tabs
|
||||
value={pathname}
|
||||
variant="fullWidth"
|
||||
onChange={handleTabChange}
|
||||
indicatorColor="primary"
|
||||
>
|
||||
<Tab label="Tokens" value="/transfer" />
|
||||
<Tab label="NFTs" value="/nft" />
|
||||
<Tab label="Redeem" value="/redeem" to="/redeem" />
|
||||
</Tabs>
|
||||
</Container>
|
||||
) : null}
|
||||
<Switch>
|
||||
<Route exact path="/transfer">
|
||||
<Transfer />
|
||||
</Route>
|
||||
<Route exact path="/nft">
|
||||
<NFT />
|
||||
</Route>
|
||||
<Route exact path="/redeem">
|
||||
<Recovery />
|
||||
</Route>
|
||||
<Route exact path="/nft-origin-verifier">
|
||||
<NFTOriginVerifier />
|
||||
</Route>
|
||||
<Route exact path="/token-origin-verifier">
|
||||
<TokenOriginVerifier />
|
||||
</Route>
|
||||
<Route exact path="/register">
|
||||
<Attest />
|
||||
</Route>
|
||||
<Route exact path="/migrate/Solana/:legacyAsset/:fromTokenAccount">
|
||||
<Migration chainId={CHAIN_ID_SOLANA} />
|
||||
</Route>
|
||||
<Route exact path="/migrate/Ethereum/:legacyAsset/">
|
||||
<Migration chainId={CHAIN_ID_ETH} />
|
||||
</Route>
|
||||
<Route exact path="/migrate/BinanceSmartChain/:legacyAsset/">
|
||||
<Migration chainId={CHAIN_ID_BSC} />
|
||||
</Route>
|
||||
<Route exact path="/migrate/Ethereum/">
|
||||
<EvmQuickMigrate chainId={CHAIN_ID_ETH} />
|
||||
</Route>
|
||||
<Route exact path="/migrate/BinanceSmartChain/">
|
||||
<EvmQuickMigrate chainId={CHAIN_ID_BSC} />
|
||||
</Route>
|
||||
<Route exact path="/migrate/Solana/">
|
||||
<SolanaQuickMigrate />
|
||||
</Route>
|
||||
<Route exact path="/stats">
|
||||
<Stats />
|
||||
</Route>
|
||||
<Route exact path="/withdraw-tokens-terra">
|
||||
<WithdrawTokensTerra />
|
||||
</Route>
|
||||
<Route exact path="/unwrap-native">
|
||||
<UnwrapNative />
|
||||
</Route>
|
||||
<Route exact path="/custody-addresses">
|
||||
<CustodyAddresses />
|
||||
</Route>
|
||||
<Route>
|
||||
<Redirect to="/transfer" />
|
||||
</Route>
|
||||
</Switch>
|
||||
<div className={classes.spacer} />
|
||||
<div className={classes.gradientRight}></div>
|
||||
<div className={classes.gradientRight2}></div>
|
||||
|
|
|
@ -189,6 +189,16 @@ export default function Footer() {
|
|||
>
|
||||
Wormhole
|
||||
</Link>
|
||||
<Link
|
||||
component={NavLink}
|
||||
to={"/custody-addresses"}
|
||||
color="inherit"
|
||||
underline="hover"
|
||||
className={classes.linkStyle}
|
||||
activeClassName={classes.linkActiveStyle}
|
||||
>
|
||||
Custody
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.spacer} />
|
||||
|
|
|
@ -5,9 +5,7 @@ import { COLORS } from "../muiTheme";
|
|||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
centeredContainer: {
|
||||
marginTop: theme.spacing(14),
|
||||
marginBottom: theme.spacing(26),
|
||||
minHeight: 208,
|
||||
marginBottom: theme.spacing(24),
|
||||
textAlign: "center",
|
||||
width: "100%",
|
||||
},
|
||||
|
@ -19,6 +17,9 @@ const useStyles = makeStyles((theme) => ({
|
|||
MozBackgroundClip: "text",
|
||||
MozTextFillColor: "transparent",
|
||||
},
|
||||
subtitle: {
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
|
||||
export default function HeaderText({
|
||||
|
@ -39,11 +40,14 @@ export default function HeaderText({
|
|||
variant={small ? "h2" : "h1"}
|
||||
component="h1"
|
||||
className={clsx({ [classes.linearGradient]: !white })}
|
||||
gutterBottom={!!subtitle}
|
||||
>
|
||||
{children}
|
||||
</Typography>
|
||||
{subtitle ? <Typography component="div">{subtitle}</Typography> : null}
|
||||
{subtitle ? (
|
||||
<Typography component="div" className={classes.subtitle}>
|
||||
{subtitle}
|
||||
</Typography>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import { makeStyles, Typography } from "@material-ui/core";
|
||||
import { formatDate } from "./utils";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
container: {
|
||||
padding: "16px",
|
||||
minWidth: "214px",
|
||||
background: "rgba(255, 255, 255, 0.95)",
|
||||
borderRadius: "4px",
|
||||
},
|
||||
titleText: {
|
||||
color: "#21227E",
|
||||
fontSize: "24px",
|
||||
fontWeight: 500,
|
||||
},
|
||||
ruler: {
|
||||
height: "3px",
|
||||
backgroundImage: "linear-gradient(90deg, #F44B1B 0%, #EEB430 100%)",
|
||||
},
|
||||
valueText: {
|
||||
color: "#404040",
|
||||
fontSize: "18px",
|
||||
fontWeight: 500,
|
||||
},
|
||||
}));
|
||||
|
||||
const CustomTooltip = ({ active, payload, title, valueFormatter }: any) => {
|
||||
const classes = useStyles();
|
||||
if (active && payload && payload.length) {
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<Typography className={classes.titleText}>{title}</Typography>
|
||||
<hr className={classes.ruler}></hr>
|
||||
<Typography className={classes.valueText}>
|
||||
{valueFormatter(payload[0].value)}
|
||||
</Typography>
|
||||
<Typography className={classes.valueText}>
|
||||
{formatDate(payload[0].payload.date)}
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default CustomTooltip;
|
|
@ -0,0 +1,123 @@
|
|||
import { ChainId } from "@certusone/wormhole-sdk";
|
||||
import { makeStyles, Grid, Typography } from "@material-ui/core";
|
||||
import {
|
||||
getChainShortName,
|
||||
CHAINS_BY_ID,
|
||||
COLOR_BY_CHAIN_ID,
|
||||
} from "../../../utils/consts";
|
||||
import { formatDate } from "./utils";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
container: {
|
||||
padding: "16px",
|
||||
minWidth: "214px",
|
||||
background: "rgba(255, 255, 255, 0.95)",
|
||||
borderRadius: "4px",
|
||||
},
|
||||
titleText: {
|
||||
color: "#21227E",
|
||||
fontSize: "24px",
|
||||
fontWeight: 500,
|
||||
},
|
||||
row: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: "8px",
|
||||
},
|
||||
ruler: {
|
||||
height: "3px",
|
||||
backgroundColor: "#374B92",
|
||||
},
|
||||
valueText: {
|
||||
color: "#404040",
|
||||
fontSize: "18px",
|
||||
fontWeight: 500,
|
||||
},
|
||||
icon: {
|
||||
width: "24px",
|
||||
height: "24px",
|
||||
},
|
||||
}));
|
||||
|
||||
const MultiChainTooltip = ({ active, payload, title, valueFormatter }: any) => {
|
||||
const classes = useStyles();
|
||||
if (active && payload && payload.length) {
|
||||
if (payload.length === 1) {
|
||||
const chainId = +payload[0].dataKey.split(".")[1] as ChainId;
|
||||
const chainShortName = getChainShortName(chainId);
|
||||
const data = payload.find((data: any) => data.name === chainShortName);
|
||||
if (data) {
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<Grid container alignItems="center">
|
||||
<img
|
||||
className={classes.icon}
|
||||
src={CHAINS_BY_ID[chainId]?.logo}
|
||||
alt={chainShortName}
|
||||
/>
|
||||
<Typography
|
||||
display="inline"
|
||||
className={classes.titleText}
|
||||
style={{ marginLeft: "8px" }}
|
||||
>
|
||||
{chainShortName}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<hr
|
||||
className={classes.ruler}
|
||||
style={{ backgroundColor: COLOR_BY_CHAIN_ID[chainId] }}
|
||||
></hr>
|
||||
<Typography className={classes.valueText}>
|
||||
{valueFormatter(data.value)}
|
||||
</Typography>
|
||||
<Typography className={classes.valueText}>
|
||||
{formatDate(data.payload.date)}
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<Typography noWrap className={classes.titleText}>
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography className={classes.valueText}>
|
||||
{formatDate(payload[0].payload.date)}
|
||||
</Typography>
|
||||
<hr className={classes.ruler}></hr>
|
||||
{payload.map((data: any) => {
|
||||
return (
|
||||
<div key={data.name} className={classes.row}>
|
||||
<div
|
||||
style={{
|
||||
width: "24px",
|
||||
height: "24px",
|
||||
backgroundColor: data.stroke,
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
display="inline"
|
||||
className={classes.valueText}
|
||||
style={{ marginLeft: "8px", marginRight: "8px" }}
|
||||
>
|
||||
{data.name}
|
||||
</Typography>
|
||||
<Typography
|
||||
display="inline"
|
||||
className={classes.valueText}
|
||||
style={{ marginLeft: "auto" }}
|
||||
>
|
||||
{valueFormatter(data.value)}
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default MultiChainTooltip;
|
|
@ -0,0 +1,63 @@
|
|||
import {
|
||||
AreaChart,
|
||||
Area,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
import { formatTVL, createCumulativeTVLChartData } from "./utils";
|
||||
import { NotionalTVLCumulative } from "../../../hooks/useCumulativeTVL";
|
||||
import { useMemo } from "react";
|
||||
import { TimeFrame } from "./TimeFrame";
|
||||
import CustomTooltip from "./CustomTooltip";
|
||||
import { useTheme, useMediaQuery } from "@material-ui/core";
|
||||
|
||||
const TVLAreaChart = ({
|
||||
cumulativeTVL,
|
||||
timeFrame,
|
||||
}: {
|
||||
cumulativeTVL: NotionalTVLCumulative;
|
||||
timeFrame: TimeFrame;
|
||||
}) => {
|
||||
const data = useMemo(() => {
|
||||
return createCumulativeTVLChartData(cumulativeTVL, timeFrame);
|
||||
}, [cumulativeTVL, timeFrame]);
|
||||
|
||||
const theme = useTheme();
|
||||
const isXSmall = useMediaQuery(theme.breakpoints.down("xs"));
|
||||
|
||||
return (
|
||||
<ResponsiveContainer height={768}>
|
||||
<AreaChart data={data}>
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickFormatter={timeFrame.tickFormatter}
|
||||
tick={{ fill: "white" }}
|
||||
interval={!isXSmall ? timeFrame.interval : undefined}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
dy={16}
|
||||
/>
|
||||
<YAxis
|
||||
tickFormatter={formatTVL}
|
||||
tick={{ fill: "white" }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
/>
|
||||
<Tooltip
|
||||
content={<CustomTooltip title="TVL" valueFormatter={formatTVL} />}
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient id="gradient" gradientTransform="rotate(100)">
|
||||
<stop offset="0%" stopColor="#FF2B57" />
|
||||
<stop offset="100%" stopColor="#5EA1EC" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<Area dataKey="totalTVL" fill="url(#gradient)" stroke="#405BBC" />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default TVLAreaChart;
|
|
@ -0,0 +1,116 @@
|
|||
import { ChainId, CHAIN_ID_ETH } from "@certusone/wormhole-sdk";
|
||||
import {
|
||||
Button,
|
||||
makeStyles,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@material-ui/core";
|
||||
import { ArrowForward } from "@material-ui/icons";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { NotionalTVL } from "../../../hooks/useTVL";
|
||||
import { ChainInfo, getChainShortName } from "../../../utils/consts";
|
||||
import { createChainTVLChartData, formatTVL } from "./utils";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
table: {
|
||||
borderSpacing: "16px",
|
||||
overflowX: "auto",
|
||||
display: "block",
|
||||
},
|
||||
button: {
|
||||
height: "30px",
|
||||
textTransform: "none",
|
||||
width: "150px",
|
||||
fontSize: "12px",
|
||||
},
|
||||
}));
|
||||
|
||||
const TVLBarChart = ({
|
||||
tvl,
|
||||
onChainSelected,
|
||||
}: {
|
||||
tvl: NotionalTVL;
|
||||
onChainSelected: (chainInfo: ChainInfo) => void;
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
|
||||
const [mouseOverChainId, setMouseOverChainId] =
|
||||
useState<ChainId>(CHAIN_ID_ETH);
|
||||
|
||||
const chainTVLs = useMemo(() => {
|
||||
return createChainTVLChartData(tvl);
|
||||
}, [tvl]);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(chainInfo: ChainInfo) => {
|
||||
onChainSelected(chainInfo);
|
||||
},
|
||||
[onChainSelected]
|
||||
);
|
||||
|
||||
const handleMouseOver = useCallback((chainId: ChainId) => {
|
||||
setMouseOverChainId(chainId);
|
||||
}, []);
|
||||
|
||||
const theme = useTheme();
|
||||
const isSmall = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
|
||||
return (
|
||||
<table className={classes.table}>
|
||||
<tbody>
|
||||
{chainTVLs.map((chainTVL) => (
|
||||
<tr
|
||||
key={chainTVL.chainInfo.id}
|
||||
onMouseOver={() => handleMouseOver(chainTVL.chainInfo.id)}
|
||||
>
|
||||
<td style={{ textAlign: "right" }}>
|
||||
<Typography noWrap display="inline">
|
||||
{getChainShortName(chainTVL.chainInfo.id)}
|
||||
</Typography>
|
||||
</td>
|
||||
<td>
|
||||
<img
|
||||
src={chainTVL.chainInfo.logo}
|
||||
alt={""}
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
</td>
|
||||
<td width="100%">
|
||||
<div
|
||||
style={{
|
||||
height: 30,
|
||||
width: `${chainTVL.tvlRatio}%`,
|
||||
backgroundImage:
|
||||
"linear-gradient(90deg, #F44B1B 0%, #EEB430 100%)",
|
||||
}}
|
||||
></div>
|
||||
</td>
|
||||
<td>
|
||||
<Typography noWrap display="inline">
|
||||
{formatTVL(chainTVL.tvl)}
|
||||
</Typography>
|
||||
</td>
|
||||
<td>
|
||||
{isSmall || mouseOverChainId === chainTVL.chainInfo.id ? (
|
||||
<Button
|
||||
variant="outlined"
|
||||
endIcon={<ArrowForward />}
|
||||
onClick={() => handleClick(chainTVL.chainInfo)}
|
||||
className={classes.button}
|
||||
>
|
||||
View assets
|
||||
</Button>
|
||||
) : (
|
||||
<div style={{ width: 150 }} />
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
export default TVLBarChart;
|
|
@ -0,0 +1,86 @@
|
|||
import { ChainId } from "@certusone/wormhole-sdk";
|
||||
import { useTheme, useMediaQuery } from "@material-ui/core";
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
Legend,
|
||||
Line,
|
||||
LineChart,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
import { NotionalTVLCumulative } from "../../../hooks/useCumulativeTVL";
|
||||
import { COLOR_BY_CHAIN_ID, getChainShortName } from "../../../utils/consts";
|
||||
import MultiChainTooltip from "./MultiChainTooltip";
|
||||
import { TimeFrame } from "./TimeFrame";
|
||||
import {
|
||||
formatTVL,
|
||||
createCumulativeTVLChartData,
|
||||
renderLegendText,
|
||||
} from "./utils";
|
||||
|
||||
const TVLLineChart = ({
|
||||
cumulativeTVL,
|
||||
timeFrame,
|
||||
selectedChains,
|
||||
}: {
|
||||
cumulativeTVL: NotionalTVLCumulative;
|
||||
timeFrame: TimeFrame;
|
||||
selectedChains: ChainId[];
|
||||
}) => {
|
||||
const data = useMemo(() => {
|
||||
return createCumulativeTVLChartData(cumulativeTVL, timeFrame);
|
||||
}, [cumulativeTVL, timeFrame]);
|
||||
|
||||
const theme = useTheme();
|
||||
const isXSmall = useMediaQuery(theme.breakpoints.down("xs"));
|
||||
|
||||
return (
|
||||
<ResponsiveContainer height={768}>
|
||||
<LineChart data={data}>
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickFormatter={timeFrame.tickFormatter}
|
||||
tick={{ fill: "white" }}
|
||||
interval={!isXSmall ? timeFrame.interval : undefined}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
dy={16}
|
||||
/>
|
||||
<YAxis
|
||||
tickFormatter={formatTVL}
|
||||
tick={{ fill: "white" }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
/>
|
||||
<Tooltip
|
||||
content={
|
||||
<MultiChainTooltip
|
||||
title="Multiple Chains"
|
||||
valueFormatter={formatTVL}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{selectedChains.map((chainId) => (
|
||||
<Line
|
||||
dataKey={`tvlByChain.${chainId}`}
|
||||
name={getChainShortName(chainId)}
|
||||
stroke={COLOR_BY_CHAIN_ID[chainId]}
|
||||
strokeWidth="4"
|
||||
dot={false}
|
||||
key={chainId}
|
||||
/>
|
||||
))}
|
||||
<Legend
|
||||
iconType="square"
|
||||
iconSize={32}
|
||||
formatter={renderLegendText}
|
||||
wrapperStyle={{ paddingTop: 24 }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default TVLLineChart;
|
|
@ -0,0 +1,141 @@
|
|||
import { makeStyles } from "@material-ui/core";
|
||||
import numeral from "numeral";
|
||||
import { useMemo } from "react";
|
||||
import { createTVLArray, NotionalTVL } from "../../../hooks/useTVL";
|
||||
import { ChainInfo } from "../../../utils/consts";
|
||||
import SmartAddress from "../../SmartAddress";
|
||||
import MuiReactTable from "../tableComponents/MuiReactTable";
|
||||
import { formatTVL } from "./utils";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
logoPositioner: {
|
||||
height: "30px",
|
||||
width: "30px",
|
||||
maxWidth: "30px",
|
||||
marginRight: theme.spacing(1),
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
logo: {
|
||||
maxHeight: "100%",
|
||||
maxWidth: "100%",
|
||||
},
|
||||
tokenContainer: {
|
||||
display: "flex",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
},
|
||||
}));
|
||||
|
||||
const TVLTable = ({
|
||||
chainInfo,
|
||||
tvl,
|
||||
}: {
|
||||
chainInfo: ChainInfo;
|
||||
tvl: NotionalTVL;
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const chainTVL = useMemo(() => {
|
||||
return createTVLArray(tvl).filter((x) => x.originChainId === chainInfo.id);
|
||||
}, [chainInfo, tvl]);
|
||||
|
||||
const sortTokens = useMemo(() => {
|
||||
return (rowA: any, rowB: any) => {
|
||||
if (rowA.isGrouped && rowB.isGrouped) {
|
||||
return rowA.values.assetAddress > rowB.values.assetAddress ? 1 : -1;
|
||||
} else if (rowA.isGrouped && !rowB.isGrouped) {
|
||||
return 1;
|
||||
} else if (!rowA.isGrouped && rowB.isGrouped) {
|
||||
return -1;
|
||||
} else if (rowA.original.symbol && !rowB.original.symbol) {
|
||||
return 1;
|
||||
} else if (rowB.original.symbol && !rowA.original.symbol) {
|
||||
return -1;
|
||||
} else if (rowA.original.symbol && rowB.original.symbol) {
|
||||
return rowA.original.symbol > rowB.original.symbol ? 1 : -1;
|
||||
} else {
|
||||
return rowA.original.assetAddress > rowB.original.assetAddress ? 1 : -1;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
const tvlColumns = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
Header: "Token",
|
||||
id: "assetAddress",
|
||||
sortType: sortTokens,
|
||||
disableGroupBy: true,
|
||||
accessor: (value: any) => ({
|
||||
chainId: value.originChainId,
|
||||
symbol: value.symbol,
|
||||
name: value.name,
|
||||
logo: value.logo,
|
||||
assetAddress: value.assetAddress,
|
||||
}),
|
||||
Cell: (value: any) => (
|
||||
<div className={classes.tokenContainer}>
|
||||
<div className={classes.logoPositioner}>
|
||||
{value.row?.original?.logo ? (
|
||||
<img
|
||||
src={value.row?.original?.logo}
|
||||
alt=""
|
||||
className={classes.logo}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<SmartAddress
|
||||
chainId={value.row?.original?.originChainId}
|
||||
address={value.row?.original?.assetAddress}
|
||||
symbol={value.row?.original?.symbol}
|
||||
tokenName={value.row?.original?.name}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: "Quantity",
|
||||
accessor: "amount",
|
||||
disableGroupBy: true,
|
||||
Cell: (value: any) =>
|
||||
value.row?.original?.amount !== undefined
|
||||
? numeral(value.row?.original?.amount).format("0,0.00")
|
||||
: "",
|
||||
},
|
||||
{
|
||||
Header: "Unit Price",
|
||||
accessor: "quotePrice",
|
||||
disableGroupBy: true,
|
||||
Cell: (value: any) =>
|
||||
value.row?.original?.quotePrice !== undefined
|
||||
? numeral(value.row?.original?.quotePrice).format("0,0.00")
|
||||
: "",
|
||||
},
|
||||
{
|
||||
Header: "Value (USD)",
|
||||
id: "totalValue",
|
||||
accessor: "totalValue",
|
||||
disableGroupBy: true,
|
||||
Cell: (value: any) =>
|
||||
value.row?.original?.totalValue !== undefined
|
||||
? formatTVL(value.row?.original?.totalValue)
|
||||
: "",
|
||||
},
|
||||
];
|
||||
}, [
|
||||
classes.logo,
|
||||
classes.tokenContainer,
|
||||
classes.logoPositioner,
|
||||
sortTokens,
|
||||
]);
|
||||
|
||||
return (
|
||||
<MuiReactTable
|
||||
columns={tvlColumns}
|
||||
data={chainTVL || []}
|
||||
skipPageReset={false}
|
||||
initialState={{ sortBy: [{ id: "totalValue", desc: true }] }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default TVLTable;
|
|
@ -0,0 +1,34 @@
|
|||
import { DurationLike } from "luxon";
|
||||
import { formatTickDay, formatTickMonth } from "./utils";
|
||||
|
||||
export interface TimeFrame {
|
||||
interval?: number;
|
||||
duration?: DurationLike;
|
||||
tickFormatter: (value: any, index: number) => string;
|
||||
}
|
||||
|
||||
export const TIME_FRAMES: { [key: string]: TimeFrame } = {
|
||||
"7 days": {
|
||||
duration: { days: 7 },
|
||||
tickFormatter: formatTickDay,
|
||||
},
|
||||
"30 days": {
|
||||
duration: { days: 30 },
|
||||
tickFormatter: formatTickDay,
|
||||
},
|
||||
"3 months": {
|
||||
duration: { months: 3 },
|
||||
tickFormatter: formatTickDay,
|
||||
},
|
||||
"6 months": {
|
||||
duration: { months: 6 },
|
||||
interval: 30,
|
||||
tickFormatter: formatTickMonth,
|
||||
},
|
||||
"1 year": {
|
||||
duration: { years: 1 },
|
||||
interval: 30,
|
||||
tickFormatter: formatTickMonth,
|
||||
},
|
||||
"All time": { interval: 30, tickFormatter: formatTickMonth },
|
||||
};
|
|
@ -0,0 +1,68 @@
|
|||
import { useTheme, useMediaQuery } from "@material-ui/core";
|
||||
import { useCallback } from "react";
|
||||
import {
|
||||
Area,
|
||||
AreaChart,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
import CustomTooltip from "./CustomTooltip";
|
||||
import { TimeFrame } from "./TimeFrame";
|
||||
import { formatTransactionCount, TransactionData } from "./utils";
|
||||
|
||||
const TransactionsAreaChart = ({
|
||||
transactionData,
|
||||
timeFrame,
|
||||
}: {
|
||||
transactionData: TransactionData[];
|
||||
timeFrame: TimeFrame;
|
||||
}) => {
|
||||
const formatValue = useCallback((value: number) => {
|
||||
return `${formatTransactionCount(value)} transactions`;
|
||||
}, []);
|
||||
|
||||
const theme = useTheme();
|
||||
const isXSmall = useMediaQuery(theme.breakpoints.down("xs"));
|
||||
|
||||
return (
|
||||
<ResponsiveContainer height={768}>
|
||||
<AreaChart data={transactionData}>
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickFormatter={timeFrame.tickFormatter}
|
||||
tick={{ fill: "white" }}
|
||||
interval={!isXSmall ? timeFrame.interval : undefined}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
dy={16}
|
||||
/>
|
||||
<YAxis
|
||||
tickFormatter={formatTransactionCount}
|
||||
tick={{ fill: "white" }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
/>
|
||||
<Tooltip
|
||||
content={
|
||||
<CustomTooltip title="All chains" valueFormatter={formatValue} />
|
||||
}
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient id="gradient" gradientTransform="rotate(100)">
|
||||
<stop offset="0%" stopColor="#FF2B57" />
|
||||
<stop offset="100%" stopColor="#5EA1EC" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<Area
|
||||
dataKey="totalTransactions"
|
||||
stroke="#405BBC"
|
||||
fill="url(#gradient)"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default TransactionsAreaChart;
|
|
@ -0,0 +1,85 @@
|
|||
import { ChainId } from "@certusone/wormhole-sdk";
|
||||
import { useTheme, useMediaQuery } from "@material-ui/core";
|
||||
import { useCallback } from "react";
|
||||
import {
|
||||
ResponsiveContainer,
|
||||
LineChart,
|
||||
XAxis,
|
||||
YAxis,
|
||||
Line,
|
||||
Legend,
|
||||
Tooltip,
|
||||
} from "recharts";
|
||||
import { COLOR_BY_CHAIN_ID, getChainShortName } from "../../../utils/consts";
|
||||
import MultiChainTooltip from "./MultiChainTooltip";
|
||||
import { TimeFrame } from "./TimeFrame";
|
||||
import {
|
||||
formatTransactionCount,
|
||||
renderLegendText,
|
||||
TransactionData,
|
||||
} from "./utils";
|
||||
|
||||
const TransactionsLineChart = ({
|
||||
transactionData,
|
||||
timeFrame,
|
||||
chains,
|
||||
}: {
|
||||
transactionData: TransactionData[];
|
||||
timeFrame: TimeFrame;
|
||||
chains: ChainId[];
|
||||
}) => {
|
||||
const formatValue = useCallback((value: number) => {
|
||||
return `${formatTransactionCount(value)} transactions`;
|
||||
}, []);
|
||||
|
||||
const theme = useTheme();
|
||||
const isXSmall = useMediaQuery(theme.breakpoints.down("xs"));
|
||||
|
||||
return (
|
||||
<ResponsiveContainer height={768}>
|
||||
<LineChart data={transactionData}>
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickFormatter={timeFrame.tickFormatter}
|
||||
tick={{ fill: "white" }}
|
||||
interval={!isXSmall ? timeFrame.interval : undefined}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
dy={16}
|
||||
/>
|
||||
<YAxis
|
||||
tickFormatter={formatTransactionCount}
|
||||
tick={{ fill: "white" }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
/>
|
||||
<Tooltip
|
||||
content={
|
||||
<MultiChainTooltip
|
||||
title="Multiple Chains"
|
||||
valueFormatter={formatValue}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{chains.map((chainId) => (
|
||||
<Line
|
||||
dataKey={`transactionsByChain.${chainId}`}
|
||||
name={getChainShortName(chainId)}
|
||||
stroke={COLOR_BY_CHAIN_ID[chainId]}
|
||||
strokeWidth="4"
|
||||
dot={false}
|
||||
key={chainId}
|
||||
/>
|
||||
))}
|
||||
<Legend
|
||||
iconType="square"
|
||||
iconSize={32}
|
||||
formatter={renderLegendText}
|
||||
wrapperStyle={{ paddingTop: 24 }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default TransactionsLineChart;
|
|
@ -0,0 +1,63 @@
|
|||
import { useTheme, useMediaQuery } from "@material-ui/core";
|
||||
import {
|
||||
Area,
|
||||
AreaChart,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
import CustomTooltip from "./CustomTooltip";
|
||||
import { TimeFrame } from "./TimeFrame";
|
||||
import { TransferChartData, formatTVL } from "./utils";
|
||||
|
||||
const VolumeAreaChart = ({
|
||||
transferData,
|
||||
timeFrame,
|
||||
}: {
|
||||
transferData: TransferChartData[];
|
||||
timeFrame: TimeFrame;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isXSmall = useMediaQuery(theme.breakpoints.down("xs"));
|
||||
|
||||
return (
|
||||
<ResponsiveContainer height={768}>
|
||||
<AreaChart data={transferData}>
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickFormatter={timeFrame.tickFormatter}
|
||||
tick={{ fill: "white" }}
|
||||
interval={!isXSmall ? timeFrame.interval : undefined}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
dy={16}
|
||||
/>
|
||||
<YAxis
|
||||
tickFormatter={formatTVL}
|
||||
tick={{ fill: "white" }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
/>
|
||||
<Tooltip
|
||||
content={
|
||||
<CustomTooltip title="All chains" valueFormatter={formatTVL} />
|
||||
}
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient id="gradient" gradientTransform="rotate(100)">
|
||||
<stop offset="0%" stopColor="#FF2B57" />
|
||||
<stop offset="100%" stopColor="#5EA1EC" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<Area
|
||||
dataKey="totalTransferred"
|
||||
stroke="#405BBC"
|
||||
fill="url(#gradient)"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default VolumeAreaChart;
|
|
@ -0,0 +1,76 @@
|
|||
import { ChainId } from "@certusone/wormhole-sdk";
|
||||
import { useTheme, useMediaQuery } from "@material-ui/core";
|
||||
import {
|
||||
Legend,
|
||||
ResponsiveContainer,
|
||||
LineChart,
|
||||
XAxis,
|
||||
YAxis,
|
||||
Line,
|
||||
Tooltip,
|
||||
} from "recharts";
|
||||
import { COLOR_BY_CHAIN_ID, getChainShortName } from "../../../utils/consts";
|
||||
import MultiChainTooltip from "./MultiChainTooltip";
|
||||
import { TimeFrame } from "./TimeFrame";
|
||||
import { formatTVL, renderLegendText, TransferChartData } from "./utils";
|
||||
|
||||
const VolumeLineChart = ({
|
||||
transferData,
|
||||
timeFrame,
|
||||
chains,
|
||||
}: {
|
||||
transferData: TransferChartData[];
|
||||
timeFrame: TimeFrame;
|
||||
chains: ChainId[];
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isXSmall = useMediaQuery(theme.breakpoints.down("xs"));
|
||||
|
||||
return (
|
||||
<ResponsiveContainer height={768}>
|
||||
<LineChart data={transferData}>
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickFormatter={timeFrame.tickFormatter}
|
||||
tick={{ fill: "white" }}
|
||||
interval={!isXSmall ? timeFrame.interval : undefined}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
dy={16}
|
||||
/>
|
||||
<YAxis
|
||||
tickFormatter={formatTVL}
|
||||
tick={{ fill: "white" }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
/>
|
||||
<Tooltip
|
||||
content={
|
||||
<MultiChainTooltip
|
||||
title="Multiple Chains"
|
||||
valueFormatter={formatTVL}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{chains.map((chainId) => (
|
||||
<Line
|
||||
dataKey={`transferredByChain.${chainId}`}
|
||||
name={getChainShortName(chainId)}
|
||||
stroke={COLOR_BY_CHAIN_ID[chainId]}
|
||||
strokeWidth="4"
|
||||
dot={false}
|
||||
key={chainId}
|
||||
/>
|
||||
))}
|
||||
<Legend
|
||||
iconType="square"
|
||||
iconSize={32}
|
||||
formatter={renderLegendText}
|
||||
wrapperStyle={{ paddingTop: 24 }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default VolumeLineChart;
|
|
@ -0,0 +1,196 @@
|
|||
import { ChainId } from "@certusone/wormhole-sdk";
|
||||
import {
|
||||
Typography,
|
||||
makeStyles,
|
||||
Grid,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@material-ui/core";
|
||||
import { useMemo, useState } from "react";
|
||||
import {
|
||||
ResponsiveContainer,
|
||||
BarChart,
|
||||
XAxis,
|
||||
YAxis,
|
||||
Tooltip,
|
||||
Bar,
|
||||
Legend,
|
||||
} from "recharts";
|
||||
import {
|
||||
CHAINS_BY_ID,
|
||||
COLOR_BY_CHAIN_ID,
|
||||
getChainShortName,
|
||||
} from "../../../utils/consts";
|
||||
import { TimeFrame } from "./TimeFrame";
|
||||
import { formatDate, TransferChartData, formatTVL, renderLegendText } from "./utils";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
tooltipContainer: {
|
||||
padding: "16px",
|
||||
minWidth: "214px",
|
||||
background: "rgba(255, 255, 255, 0.95)",
|
||||
borderRadius: "4px",
|
||||
},
|
||||
tooltipTitleText: {
|
||||
color: "#21227E",
|
||||
fontSize: "24px",
|
||||
fontWeight: 500,
|
||||
marginLeft: "8px",
|
||||
},
|
||||
tooltipRuler: {
|
||||
height: "3px",
|
||||
},
|
||||
tooltipValueText: {
|
||||
color: "#404040",
|
||||
fontSize: "18px",
|
||||
fontWeight: 500,
|
||||
},
|
||||
tooltipIcon: {
|
||||
width: "24px",
|
||||
height: "24px",
|
||||
},
|
||||
}));
|
||||
|
||||
interface BarData {
|
||||
date: Date;
|
||||
volume: {
|
||||
[chainId: string]: number;
|
||||
};
|
||||
volumePercent: {
|
||||
[chainId: string]: number;
|
||||
};
|
||||
}
|
||||
|
||||
const createBarData = (
|
||||
transferData: TransferChartData[],
|
||||
selectedChains: ChainId[]
|
||||
) => {
|
||||
return transferData.reduce<BarData[]>((barData, transfer) => {
|
||||
const data: BarData = {
|
||||
date: transfer.date,
|
||||
volume: {},
|
||||
volumePercent: {},
|
||||
};
|
||||
const totalVolume = Object.entries(transfer.transferredByChain).reduce(
|
||||
(totalVolume, [chainId, volume]) => {
|
||||
if (selectedChains.indexOf(+chainId as ChainId) > -1) {
|
||||
data.volume[chainId] = volume;
|
||||
return totalVolume + volume;
|
||||
}
|
||||
return totalVolume;
|
||||
},
|
||||
0
|
||||
);
|
||||
if (totalVolume > 0) {
|
||||
Object.keys(data.volume).forEach((chainId) => {
|
||||
data.volumePercent[chainId] =
|
||||
(data.volume[chainId] / totalVolume) * 100;
|
||||
});
|
||||
}
|
||||
barData.push(data);
|
||||
return barData;
|
||||
}, []);
|
||||
};
|
||||
|
||||
const CustomTooltip = ({ active, payload, chainId }: any) => {
|
||||
const classes = useStyles();
|
||||
if (active && payload && payload.length && chainId) {
|
||||
const chainShortName = getChainShortName(chainId);
|
||||
const data = payload.find((data: any) => data.name === chainShortName);
|
||||
if (data) {
|
||||
return (
|
||||
<div className={classes.tooltipContainer}>
|
||||
<Grid container alignItems="center">
|
||||
<img
|
||||
className={classes.tooltipIcon}
|
||||
src={CHAINS_BY_ID[chainId as ChainId]?.logo}
|
||||
alt={chainShortName}
|
||||
/>
|
||||
<Typography display="inline" className={classes.tooltipTitleText}>
|
||||
{chainShortName}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<hr
|
||||
className={classes.tooltipRuler}
|
||||
style={{ backgroundColor: COLOR_BY_CHAIN_ID[chainId as ChainId] }}
|
||||
></hr>
|
||||
<Typography
|
||||
className={classes.tooltipValueText}
|
||||
>{`${data.value.toFixed(1)}%`}</Typography>
|
||||
<Typography className={classes.tooltipValueText}>
|
||||
{formatTVL(data.payload.volume[chainId])}
|
||||
</Typography>
|
||||
<Typography className={classes.tooltipValueText}>
|
||||
{formatDate(data.payload.date)}
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const VolumeStackedBarChart = ({
|
||||
transferData,
|
||||
timeFrame,
|
||||
selectedChains,
|
||||
}: {
|
||||
transferData: TransferChartData[];
|
||||
timeFrame: TimeFrame;
|
||||
selectedChains: ChainId[];
|
||||
}) => {
|
||||
const [hoverChainId, setHoverChainId] = useState<ChainId | null>(null);
|
||||
|
||||
const barData = useMemo(() => {
|
||||
return createBarData(transferData, selectedChains);
|
||||
}, [transferData, selectedChains]);
|
||||
|
||||
const theme = useTheme();
|
||||
const isXSmall = useMediaQuery(theme.breakpoints.down("xs"));
|
||||
|
||||
return (
|
||||
<ResponsiveContainer height={768}>
|
||||
<BarChart data={barData}>
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickFormatter={timeFrame.tickFormatter}
|
||||
tick={{ fill: "white" }}
|
||||
interval={!isXSmall ? timeFrame.interval : undefined}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
dy={16}
|
||||
/>
|
||||
<YAxis
|
||||
tickFormatter={(tick) => `${tick}%`}
|
||||
ticks={[0, 25, 50, 75, 100]}
|
||||
domain={[0, 100]}
|
||||
tick={{ fill: "white" }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
/>
|
||||
<Tooltip
|
||||
content={<CustomTooltip chainId={hoverChainId} barData={barData} />}
|
||||
cursor={{ fill: "transparent" }}
|
||||
/>
|
||||
{selectedChains.map((chainId) => (
|
||||
<Bar
|
||||
dataKey={`volumePercent.${chainId}`}
|
||||
name={getChainShortName(chainId)}
|
||||
fill={COLOR_BY_CHAIN_ID[chainId]}
|
||||
key={chainId}
|
||||
stackId="a"
|
||||
onMouseOver={() => setHoverChainId(chainId)}
|
||||
/>
|
||||
))}
|
||||
<Legend
|
||||
iconType="square"
|
||||
iconSize={32}
|
||||
formatter={renderLegendText}
|
||||
wrapperStyle={{ paddingTop: 24 }}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default VolumeStackedBarChart;
|
|
@ -0,0 +1,212 @@
|
|||
import { NotionalTVLCumulative } from "../../../hooks/useCumulativeTVL";
|
||||
import { NotionalTransferredFrom } from "../../../hooks/useNotionalTransferred";
|
||||
import { TimeFrame } from "./TimeFrame";
|
||||
import { DateTime } from "luxon";
|
||||
import { Totals } from "../../../hooks/useTransactionTotals";
|
||||
import {
|
||||
ChainInfo,
|
||||
CHAINS_BY_ID,
|
||||
VAA_EMITTER_ADDRESSES,
|
||||
} from "../../../utils/consts";
|
||||
import { NotionalTVL } from "../../../hooks/useTVL";
|
||||
import { ChainId } from "@certusone/wormhole-sdk";
|
||||
|
||||
export const formatTVL = (tvl: number) => {
|
||||
const [divisor, unit, fractionDigits] =
|
||||
tvl < 1e3
|
||||
? [1, "", 0]
|
||||
: tvl < 1e6
|
||||
? [1e3, "K", 0]
|
||||
: tvl < 1e9
|
||||
? [1e6, "M", 0]
|
||||
: [1e9, "B", 2];
|
||||
return `$${(tvl / divisor).toFixed(fractionDigits)} ${unit}`;
|
||||
};
|
||||
|
||||
export const formatDate = (date: Date) => {
|
||||
return date.toLocaleString("en-US", {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
timeZone: "UTC",
|
||||
});
|
||||
};
|
||||
|
||||
export const formatTickDay = (date: Date) => {
|
||||
return date.toLocaleString("en-US", {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
timeZone: "UTC",
|
||||
});
|
||||
};
|
||||
|
||||
export const formatTickMonth = (date: Date) => {
|
||||
return date.toLocaleString("en-US", {
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
timeZone: "UTC",
|
||||
});
|
||||
};
|
||||
|
||||
export const formatTransactionCount = (transactionCount: number) => {
|
||||
return transactionCount.toLocaleString("en-US");
|
||||
};
|
||||
|
||||
export const renderLegendText = (value: any) => {
|
||||
return <span style={{ color: "white", margin: "8px" }}>{value}</span>;
|
||||
};
|
||||
|
||||
export const getStartDate = (timeFrame: TimeFrame) => {
|
||||
return timeFrame.duration
|
||||
? DateTime.now().toUTC().minus(timeFrame.duration).toJSDate()
|
||||
: undefined;
|
||||
};
|
||||
|
||||
export interface CumulativeTVLChartData {
|
||||
date: Date;
|
||||
totalTVL: number;
|
||||
tvlByChain: {
|
||||
[chainId: string]: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const createCumulativeTVLChartData = (
|
||||
cumulativeTVL: NotionalTVLCumulative,
|
||||
timeFrame: TimeFrame
|
||||
) => {
|
||||
const startDate = getStartDate(timeFrame);
|
||||
return Object.entries(cumulativeTVL.DailyLocked)
|
||||
.reduce<CumulativeTVLChartData[]>(
|
||||
(chartData, [dateString, chainsAssets]) => {
|
||||
const date = new Date(dateString);
|
||||
if (!startDate || date >= startDate) {
|
||||
const data: CumulativeTVLChartData = {
|
||||
date: date,
|
||||
totalTVL: 0,
|
||||
tvlByChain: {},
|
||||
};
|
||||
Object.entries(chainsAssets).forEach(([chainId, lockedAssets]) => {
|
||||
const notional = lockedAssets["*"].Notional;
|
||||
if (chainId === "*") {
|
||||
data.totalTVL = notional;
|
||||
} else {
|
||||
data.tvlByChain[chainId] = notional;
|
||||
}
|
||||
});
|
||||
chartData.push(data);
|
||||
}
|
||||
return chartData;
|
||||
},
|
||||
[]
|
||||
)
|
||||
.sort((a, z) => a.date.getTime() - z.date.getTime());
|
||||
};
|
||||
|
||||
export interface TransferChartData {
|
||||
date: Date;
|
||||
totalTransferred: number;
|
||||
transferredByChain: {
|
||||
[chainId: string]: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const createCumulativeTransferChartData = (
|
||||
notionalTransferredFrom: NotionalTransferredFrom,
|
||||
timeFrame: TimeFrame
|
||||
) => {
|
||||
const startDate = getStartDate(timeFrame);
|
||||
return Object.keys(notionalTransferredFrom.Daily)
|
||||
.sort()
|
||||
.reduce<TransferChartData[]>((chartData, dateString) => {
|
||||
const transferFromData = notionalTransferredFrom.Daily[dateString];
|
||||
const data: TransferChartData = {
|
||||
date: new Date(dateString),
|
||||
totalTransferred: 0,
|
||||
transferredByChain: {},
|
||||
};
|
||||
Object.entries(transferFromData).forEach(([chainId, amount]) => {
|
||||
if (chainId === "*") {
|
||||
data.totalTransferred =
|
||||
(chartData[chartData.length - 1]?.totalTransferred || 0) + amount;
|
||||
} else {
|
||||
data.transferredByChain[chainId] =
|
||||
(chartData[chartData.length - 1]?.transferredByChain[chainId] ||
|
||||
0) + amount;
|
||||
}
|
||||
});
|
||||
chartData.push(data);
|
||||
return chartData;
|
||||
}, [])
|
||||
.filter((value) => !startDate || startDate <= value.date);
|
||||
};
|
||||
|
||||
export interface TransactionData {
|
||||
date: Date;
|
||||
totalTransactions: number;
|
||||
transactionsByChain: {
|
||||
[chainId: string]: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const createCumulativeTransactionData = (
|
||||
totals: Totals,
|
||||
timeFrame: TimeFrame
|
||||
) => {
|
||||
const startDate = getStartDate(timeFrame);
|
||||
return Object.keys(totals.DailyTotals)
|
||||
.sort()
|
||||
.reduce<TransactionData[]>((chartData, dateString) => {
|
||||
const groupByKeys = totals.DailyTotals[dateString];
|
||||
const prevData = chartData[chartData.length - 1];
|
||||
const data: TransactionData = {
|
||||
date: new Date(dateString),
|
||||
totalTransactions: prevData?.totalTransactions || 0,
|
||||
transactionsByChain: {},
|
||||
};
|
||||
VAA_EMITTER_ADDRESSES.forEach((address) => {
|
||||
const count = groupByKeys[address] || 0;
|
||||
data.totalTransactions += count;
|
||||
const chainId = address.slice(0, address.indexOf(":"));
|
||||
if (data.transactionsByChain[chainId] === undefined) {
|
||||
data.transactionsByChain[chainId] =
|
||||
prevData?.transactionsByChain[chainId] || 0;
|
||||
}
|
||||
data.transactionsByChain[chainId] += count;
|
||||
});
|
||||
chartData.push(data);
|
||||
return chartData;
|
||||
}, [])
|
||||
.filter((value) => !startDate || startDate <= value.date);
|
||||
};
|
||||
|
||||
export interface ChainTVLChartData {
|
||||
chainInfo: ChainInfo;
|
||||
tvl: number;
|
||||
tvlRatio: number;
|
||||
}
|
||||
|
||||
export const createChainTVLChartData = (tvl: NotionalTVL) => {
|
||||
let maxTVL = 0;
|
||||
const chainTVLs = Object.entries(tvl.AllTime)
|
||||
.reduce<ChainTVLChartData[]>((chartData, [chainId, assets]) => {
|
||||
const chainInfo = CHAINS_BY_ID[+chainId as ChainId];
|
||||
if (chainInfo !== undefined) {
|
||||
const tvl = assets["*"].Notional;
|
||||
chartData.push({
|
||||
chainInfo: chainInfo,
|
||||
tvl: tvl,
|
||||
tvlRatio: 0,
|
||||
});
|
||||
maxTVL = Math.max(maxTVL, tvl);
|
||||
}
|
||||
return chartData;
|
||||
}, [])
|
||||
.sort((a, z) => z.tvl - a.tvl);
|
||||
if (maxTVL > 0) {
|
||||
chainTVLs.forEach((chainTVL) => {
|
||||
chainTVL.tvlRatio = (chainTVL.tvl / maxTVL) * 100;
|
||||
});
|
||||
}
|
||||
return chainTVLs;
|
||||
};
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
CHAIN_ID_AURORA,
|
||||
CHAIN_ID_AVAX,
|
||||
CHAIN_ID_BSC,
|
||||
CHAIN_ID_ETH,
|
||||
|
@ -8,7 +9,7 @@ import {
|
|||
CHAIN_ID_SOLANA,
|
||||
CHAIN_ID_TERRA,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { makeStyles, Paper, Typography } from "@material-ui/core";
|
||||
import { Container, makeStyles, Paper, Typography } from "@material-ui/core";
|
||||
import { useMemo } from "react";
|
||||
import { COLORS } from "../../muiTheme";
|
||||
import {
|
||||
|
@ -17,6 +18,7 @@ import {
|
|||
SOL_CUSTODY_ADDRESS,
|
||||
SOL_NFT_CUSTODY_ADDRESS,
|
||||
} from "../../utils/consts";
|
||||
import HeaderText from "../HeaderText";
|
||||
import SmartAddress from "../SmartAddress";
|
||||
import MuiReactTable from "./tableComponents/MuiReactTable";
|
||||
|
||||
|
@ -97,6 +99,12 @@ const CustodyAddresses: React.FC<any> = () => {
|
|||
tokenAddress: getTokenBridgeAddressForChain(CHAIN_ID_FANTOM),
|
||||
nftAddress: getNFTBridgeAddressForChain(CHAIN_ID_FANTOM),
|
||||
},
|
||||
{
|
||||
chainName: "Aurora",
|
||||
chainId: CHAIN_ID_AURORA,
|
||||
tokenAddress: getTokenBridgeAddressForChain(CHAIN_ID_AURORA),
|
||||
nftAddress: getNFTBridgeAddressForChain(CHAIN_ID_AURORA),
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
|
||||
|
@ -159,10 +167,13 @@ const CustodyAddresses: React.FC<any> = () => {
|
|||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container maxWidth="lg">
|
||||
<Container maxWidth="md">
|
||||
<HeaderText white>Custody</HeaderText>
|
||||
</Container>
|
||||
{header}
|
||||
<Paper className={classes.mainPaper}>{table}</Paper>
|
||||
</>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,298 @@
|
|||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
CircularProgress,
|
||||
FormControl,
|
||||
ListItemText,
|
||||
makeStyles,
|
||||
MenuItem,
|
||||
Paper,
|
||||
Select,
|
||||
TextField,
|
||||
Tooltip,
|
||||
Typography,
|
||||
withStyles,
|
||||
} from "@material-ui/core";
|
||||
import { ToggleButton, ToggleButtonGroup } from "@material-ui/lab";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import TVLAreaChart from "./Charts/TVLAreaChart";
|
||||
import useCumulativeTVL from "../../hooks/useCumulativeTVL";
|
||||
import { TIME_FRAMES } from "./Charts/TimeFrame";
|
||||
import TVLLineChart from "./Charts/TVLLineChart";
|
||||
import { ChainInfo, CHAINS_BY_ID } from "../../utils/consts";
|
||||
import { ChainId } from "@certusone/wormhole-sdk";
|
||||
import { COLORS } from "../../muiTheme";
|
||||
import TVLBarChart from "./Charts/TVLBarChart";
|
||||
import TVLTable from "./Charts/TVLTable";
|
||||
import useTVL from "../../hooks/useTVL";
|
||||
import { ArrowBack, InfoOutlined } from "@material-ui/icons";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
description: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: "16px",
|
||||
[theme.breakpoints.down("xs")]: {
|
||||
flexDirection: "column",
|
||||
},
|
||||
},
|
||||
displayBy: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
flexWrap: "wrap",
|
||||
marginBottom: "16px",
|
||||
[theme.breakpoints.down("xs")]: {
|
||||
justifyContent: "center",
|
||||
columnGap: 8,
|
||||
rowGap: 8,
|
||||
},
|
||||
},
|
||||
mainPaper: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
backgroundColor: COLORS.whiteWithTransparency,
|
||||
padding: "2rem",
|
||||
marginBottom: theme.spacing(8),
|
||||
borderRadius: 8,
|
||||
},
|
||||
toggleButton: {
|
||||
textTransform: "none",
|
||||
},
|
||||
tooltip: {
|
||||
margin: 8,
|
||||
},
|
||||
alignCenter: {
|
||||
margin: "0 auto",
|
||||
display: "block",
|
||||
},
|
||||
}));
|
||||
|
||||
const tooltipStyles = {
|
||||
tooltip: {
|
||||
minWidth: "max-content",
|
||||
borderRadius: "4px",
|
||||
backgroundColor: "#5EA1EC",
|
||||
color: "#0F0C48",
|
||||
fontSize: "14px",
|
||||
},
|
||||
};
|
||||
|
||||
const StyledTooltip = withStyles(tooltipStyles)(Tooltip);
|
||||
|
||||
const DISPLAY_BY_VALUES = ["Time", "Chain"];
|
||||
|
||||
const TVLStats = () => {
|
||||
const classes = useStyles();
|
||||
|
||||
const [displayBy, setDisplayBy] = useState(DISPLAY_BY_VALUES[0]);
|
||||
const [timeFrame, setTimeFrame] = useState("All time");
|
||||
|
||||
const [selectedChains, setSelectedChains] = useState<ChainId[]>([]);
|
||||
|
||||
const [selectedChainDetail, setSelectedChainDetail] =
|
||||
useState<ChainInfo | null>(null);
|
||||
|
||||
const cumulativeTVL = useCumulativeTVL();
|
||||
const tvl = useTVL();
|
||||
|
||||
const tvlAllTime = useMemo(() => {
|
||||
return tvl.data
|
||||
? new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
maximumFractionDigits: 0,
|
||||
}).format(
|
||||
tvl.data.AllTime[selectedChainDetail?.id || "*"]["*"].Notional || 0
|
||||
)
|
||||
: "";
|
||||
}, [selectedChainDetail, tvl]);
|
||||
|
||||
const availableChains = useMemo(() => {
|
||||
const chainIds = cumulativeTVL.data
|
||||
? Object.keys(
|
||||
Object.values(cumulativeTVL.data.DailyLocked)[0] || {}
|
||||
).reduce<ChainId[]>((chainIds, key) => {
|
||||
if (key !== "*") {
|
||||
const chainId = parseInt(key) as ChainId;
|
||||
if (CHAINS_BY_ID[chainId]) {
|
||||
chainIds.push(chainId);
|
||||
}
|
||||
}
|
||||
return chainIds;
|
||||
}, [])
|
||||
: [];
|
||||
setSelectedChains(chainIds);
|
||||
return chainIds;
|
||||
}, [cumulativeTVL]);
|
||||
|
||||
const handleDisplayByChange = useCallback((event, nextValue) => {
|
||||
if (nextValue) {
|
||||
setDisplayBy(nextValue);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleTimeFrameChange = useCallback(
|
||||
(event) => setTimeFrame(event.target.value),
|
||||
[]
|
||||
);
|
||||
|
||||
const handleSelectedChainsChange = useCallback(
|
||||
(event) => {
|
||||
const value = event.target.value;
|
||||
if (value[value.length - 1] === "all") {
|
||||
setSelectedChains((prevValue) =>
|
||||
prevValue.length === availableChains.length ? [] : availableChains
|
||||
);
|
||||
} else {
|
||||
setSelectedChains(value);
|
||||
}
|
||||
},
|
||||
[availableChains]
|
||||
);
|
||||
|
||||
const handleChainDetailSelected = useCallback((chainInfo: ChainInfo) => {
|
||||
setSelectedChainDetail(chainInfo);
|
||||
}, []);
|
||||
|
||||
const allChainsSelected = selectedChains.length === availableChains.length;
|
||||
const tvlText =
|
||||
"Total Value Locked" +
|
||||
(selectedChainDetail ? ` on ${selectedChainDetail?.name}` : "");
|
||||
const tooltipText = selectedChainDetail
|
||||
? `Total Value Locked on ${selectedChainDetail?.name}`
|
||||
: "USD equivalent value of all assets locked in Portal";
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classes.description}>
|
||||
<Typography variant="h3">
|
||||
{tvlText}
|
||||
<StyledTooltip title={tooltipText} className={classes.tooltip}>
|
||||
<InfoOutlined />
|
||||
</StyledTooltip>
|
||||
</Typography>
|
||||
<Typography variant="h3">{tvlAllTime}</Typography>
|
||||
</div>
|
||||
<div className={classes.displayBy}>
|
||||
{!selectedChainDetail ? (
|
||||
<div>
|
||||
<Typography display="inline" style={{ marginRight: "8px" }}>
|
||||
Display by
|
||||
</Typography>
|
||||
<ToggleButtonGroup
|
||||
value={displayBy}
|
||||
exclusive
|
||||
onChange={handleDisplayByChange}
|
||||
>
|
||||
{DISPLAY_BY_VALUES.map((value) => (
|
||||
<ToggleButton
|
||||
key={value}
|
||||
value={value}
|
||||
className={classes.toggleButton}
|
||||
>
|
||||
{value}
|
||||
</ToggleButton>
|
||||
))}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
) : null}
|
||||
{displayBy === "Time" && !selectedChainDetail ? (
|
||||
<div>
|
||||
<FormControl>
|
||||
<Select
|
||||
multiple
|
||||
variant="outlined"
|
||||
value={selectedChains}
|
||||
onChange={handleSelectedChainsChange}
|
||||
renderValue={(selected: any) =>
|
||||
selected.length === availableChains.length
|
||||
? "All chains"
|
||||
: selected.length > 1
|
||||
? `${selected.length} chains`
|
||||
: //@ts-ignore
|
||||
CHAINS_BY_ID[selected[0]]?.name
|
||||
}
|
||||
MenuProps={{ getContentAnchorEl: null }} // hack to prevent popup menu from moving
|
||||
style={{ minWidth: 128 }}
|
||||
>
|
||||
<MenuItem value="all">
|
||||
<Checkbox
|
||||
checked={availableChains.length > 0 && allChainsSelected}
|
||||
indeterminate={
|
||||
selectedChains.length > 0 &&
|
||||
selectedChains.length < availableChains.length
|
||||
}
|
||||
/>
|
||||
<ListItemText primary="All chains" />
|
||||
</MenuItem>
|
||||
{availableChains.map((option) => (
|
||||
<MenuItem key={option} value={option}>
|
||||
<Checkbox checked={selectedChains.indexOf(option) > -1} />
|
||||
<ListItemText primary={CHAINS_BY_ID[option]?.name} />
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField
|
||||
select
|
||||
variant="outlined"
|
||||
value={timeFrame}
|
||||
onChange={handleTimeFrameChange}
|
||||
style={{ marginLeft: 8 }}
|
||||
>
|
||||
{Object.keys(TIME_FRAMES).map((timeFrame) => (
|
||||
<MenuItem key={timeFrame} value={timeFrame}>
|
||||
{timeFrame}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</div>
|
||||
) : selectedChainDetail ? (
|
||||
<Button
|
||||
startIcon={<ArrowBack />}
|
||||
onClick={() => {
|
||||
setSelectedChainDetail(null);
|
||||
}}
|
||||
>
|
||||
Back to all chains
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
<Paper className={classes.mainPaper}>
|
||||
{displayBy === "Time" ? (
|
||||
cumulativeTVL.data ? (
|
||||
allChainsSelected ? (
|
||||
<TVLAreaChart
|
||||
cumulativeTVL={cumulativeTVL.data}
|
||||
timeFrame={TIME_FRAMES[timeFrame]}
|
||||
/>
|
||||
) : (
|
||||
<TVLLineChart
|
||||
cumulativeTVL={cumulativeTVL.data}
|
||||
timeFrame={TIME_FRAMES[timeFrame]}
|
||||
selectedChains={selectedChains}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<CircularProgress className={classes.alignCenter} />
|
||||
)
|
||||
) : tvl.data ? (
|
||||
selectedChainDetail ? (
|
||||
<TVLTable chainInfo={selectedChainDetail} tvl={tvl.data} />
|
||||
) : (
|
||||
<TVLBarChart
|
||||
tvl={tvl.data}
|
||||
onChainSelected={handleChainDetailSelected}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<CircularProgress className={classes.alignCenter} />
|
||||
)}
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TVLStats;
|
|
@ -0,0 +1,325 @@
|
|||
import { ChainId } from "@certusone/wormhole-sdk";
|
||||
import {
|
||||
Checkbox,
|
||||
CircularProgress,
|
||||
FormControl,
|
||||
ListItemText,
|
||||
makeStyles,
|
||||
MenuItem,
|
||||
Paper,
|
||||
Select,
|
||||
TextField,
|
||||
Tooltip,
|
||||
Typography,
|
||||
withStyles,
|
||||
} from "@material-ui/core";
|
||||
import { InfoOutlined } from "@material-ui/icons";
|
||||
import { ToggleButton, ToggleButtonGroup } from "@material-ui/lab";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import useNotionalTransferred from "../../hooks/useNotionalTransferred";
|
||||
import { COLORS } from "../../muiTheme";
|
||||
import { CHAINS_BY_ID } from "../../utils/consts";
|
||||
import { TIME_FRAMES } from "./Charts/TimeFrame";
|
||||
import {
|
||||
createCumulativeTransferChartData,
|
||||
createCumulativeTransactionData,
|
||||
formatTransactionCount,
|
||||
} from "./Charts/utils";
|
||||
import VolumeAreaChart from "./Charts/VolumeAreaChart";
|
||||
import VolumeStackedBarChart from "./Charts/VolumeStackedBarChart";
|
||||
import VolumeLineChart from "./Charts/VolumeLineChart";
|
||||
import TransactionsAreaChart from "./Charts/TransactionsAreaChart";
|
||||
import TransactionsLineChart from "./Charts/TransactionsLineChart";
|
||||
import useTransactionTotals from "../../hooks/useTransactionTotals";
|
||||
|
||||
const DISPLAY_BY_VALUES = ["Dollar", "Percent", "Transactions"];
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
description: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: "16px",
|
||||
[theme.breakpoints.down("xs")]: {
|
||||
flexDirection: "column",
|
||||
},
|
||||
},
|
||||
displayBy: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
flexWrap: "wrap",
|
||||
marginBottom: "16px",
|
||||
[theme.breakpoints.down("xs")]: {
|
||||
justifyContent: "center",
|
||||
columnGap: 8,
|
||||
rowGap: 8,
|
||||
},
|
||||
},
|
||||
mainPaper: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
backgroundColor: COLORS.whiteWithTransparency,
|
||||
padding: "2rem",
|
||||
marginBottom: theme.spacing(8),
|
||||
borderRadius: 8,
|
||||
},
|
||||
toggleButton: {
|
||||
textTransform: "none",
|
||||
},
|
||||
tooltip: {
|
||||
margin: 8,
|
||||
},
|
||||
alignCenter: {
|
||||
margin: "0 auto",
|
||||
display: "block",
|
||||
},
|
||||
}));
|
||||
|
||||
const tooltipStyles = {
|
||||
tooltip: {
|
||||
minWidth: "max-content",
|
||||
borderRadius: "4px",
|
||||
backgroundColor: "#5EA1EC",
|
||||
color: "#0F0C48",
|
||||
fontSize: "14px",
|
||||
},
|
||||
};
|
||||
|
||||
const StyledTooltip = withStyles(tooltipStyles)(Tooltip);
|
||||
|
||||
const VolumeStats = () => {
|
||||
const classes = useStyles();
|
||||
|
||||
const [displayBy, setDisplayBy] = useState(DISPLAY_BY_VALUES[0]);
|
||||
const [timeFrame, setTimeFrame] = useState("All time");
|
||||
|
||||
const [selectedChains, setSelectedChains] = useState<ChainId[]>([]);
|
||||
|
||||
const notionalTransferred = useNotionalTransferred();
|
||||
|
||||
const transferData = useMemo(() => {
|
||||
return notionalTransferred.data
|
||||
? createCumulativeTransferChartData(
|
||||
notionalTransferred.data,
|
||||
TIME_FRAMES[timeFrame]
|
||||
)
|
||||
: [];
|
||||
}, [notionalTransferred, timeFrame]);
|
||||
|
||||
const transferredAllTime = useMemo(() => {
|
||||
return notionalTransferred.data
|
||||
? new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
maximumFractionDigits: 0,
|
||||
}).format(notionalTransferred.data.Total || 0)
|
||||
: "";
|
||||
}, [notionalTransferred]);
|
||||
|
||||
const transactionTotals = useTransactionTotals();
|
||||
|
||||
const transactionData = useMemo(() => {
|
||||
return transactionTotals.data
|
||||
? createCumulativeTransactionData(
|
||||
transactionTotals.data,
|
||||
TIME_FRAMES[timeFrame]
|
||||
)
|
||||
: [];
|
||||
}, [transactionTotals, timeFrame]);
|
||||
|
||||
const transactionsAllTime = useMemo(() => {
|
||||
const totalTransactions =
|
||||
transactionData[transactionData.length - 1]?.totalTransactions;
|
||||
return totalTransactions !== undefined
|
||||
? formatTransactionCount(totalTransactions)
|
||||
: "";
|
||||
}, [transactionData]);
|
||||
|
||||
const availableChains = useMemo(() => {
|
||||
const chainIds = notionalTransferred.data
|
||||
? Object.keys(
|
||||
Object.values(notionalTransferred.data.Daily)[0] || {}
|
||||
).reduce<ChainId[]>((chainIds, key) => {
|
||||
if (key !== "*") {
|
||||
const chainId = parseInt(key) as ChainId;
|
||||
if (CHAINS_BY_ID[chainId] !== undefined) {
|
||||
chainIds.push(chainId);
|
||||
}
|
||||
}
|
||||
return chainIds;
|
||||
}, [])
|
||||
: [];
|
||||
setSelectedChains(chainIds);
|
||||
return chainIds;
|
||||
}, [notionalTransferred]);
|
||||
|
||||
const handleDisplayByChange = useCallback((event, nextValue) => {
|
||||
if (nextValue !== null) {
|
||||
setDisplayBy(nextValue);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleTimeFrameChange = useCallback(
|
||||
(event) => setTimeFrame(event.target.value),
|
||||
[]
|
||||
);
|
||||
|
||||
const handleSelectedChainsChange = useCallback(
|
||||
(event) => {
|
||||
const value = event.target.value;
|
||||
if (value[value.length - 1] === "all") {
|
||||
setSelectedChains((prevValue) =>
|
||||
prevValue.length === availableChains.length ? [] : availableChains
|
||||
);
|
||||
} else {
|
||||
setSelectedChains(value);
|
||||
}
|
||||
},
|
||||
[availableChains]
|
||||
);
|
||||
|
||||
const allChainsSelected = selectedChains.length === availableChains.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classes.description}>
|
||||
<Typography variant="h3">
|
||||
{displayBy === "Transactions"
|
||||
? "Transaction Count"
|
||||
: "Outbound Volume"}
|
||||
<StyledTooltip
|
||||
title={
|
||||
displayBy === "Transactions"
|
||||
? "Total number of transactions the Token Bridge has processed"
|
||||
: "Amount of assets bridged through Portal in the outbound direction"
|
||||
}
|
||||
className={classes.tooltip}
|
||||
>
|
||||
<InfoOutlined />
|
||||
</StyledTooltip>
|
||||
</Typography>
|
||||
<Typography variant="h3">
|
||||
{displayBy === "Transactions"
|
||||
? transactionsAllTime
|
||||
: transferredAllTime}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.displayBy}>
|
||||
<div>
|
||||
<Typography display="inline" style={{ marginRight: "8px" }}>
|
||||
Display by
|
||||
</Typography>
|
||||
<ToggleButtonGroup
|
||||
value={displayBy}
|
||||
exclusive
|
||||
onChange={handleDisplayByChange}
|
||||
>
|
||||
{DISPLAY_BY_VALUES.map((value) => (
|
||||
<ToggleButton
|
||||
key={value}
|
||||
value={value}
|
||||
className={classes.toggleButton}
|
||||
>
|
||||
{value}
|
||||
</ToggleButton>
|
||||
))}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
<div>
|
||||
<FormControl>
|
||||
<Select
|
||||
multiple
|
||||
variant="outlined"
|
||||
value={selectedChains}
|
||||
onChange={handleSelectedChainsChange}
|
||||
renderValue={(selected: any) =>
|
||||
selected.length === availableChains.length
|
||||
? "All chains"
|
||||
: selected.length > 1
|
||||
? `${selected.length} chains`
|
||||
: //@ts-ignore
|
||||
CHAINS_BY_ID[selected[0]]?.name
|
||||
}
|
||||
MenuProps={{ getContentAnchorEl: null }} // hack to prevent popup menu from moving
|
||||
style={{ minWidth: 128 }}
|
||||
>
|
||||
<MenuItem value="all">
|
||||
<Checkbox
|
||||
checked={availableChains.length > 0 && allChainsSelected}
|
||||
indeterminate={
|
||||
selectedChains.length > 0 &&
|
||||
selectedChains.length < availableChains.length
|
||||
}
|
||||
/>
|
||||
<ListItemText primary="All chains" />
|
||||
</MenuItem>
|
||||
{availableChains.map((option) => (
|
||||
<MenuItem key={option} value={option}>
|
||||
<Checkbox checked={selectedChains.indexOf(option) > -1} />
|
||||
<ListItemText primary={CHAINS_BY_ID[option]?.name} />
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField
|
||||
select
|
||||
variant="outlined"
|
||||
value={timeFrame}
|
||||
onChange={handleTimeFrameChange}
|
||||
style={{ marginLeft: 8 }}
|
||||
>
|
||||
{Object.keys(TIME_FRAMES).map((timeFrame) => (
|
||||
<MenuItem key={timeFrame} value={timeFrame}>
|
||||
{timeFrame}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</div>
|
||||
</div>
|
||||
<Paper className={classes.mainPaper}>
|
||||
{displayBy === "Dollar" ? (
|
||||
notionalTransferred.data ? (
|
||||
allChainsSelected ? (
|
||||
<VolumeAreaChart
|
||||
transferData={transferData}
|
||||
timeFrame={TIME_FRAMES[timeFrame]}
|
||||
/>
|
||||
) : (
|
||||
<VolumeLineChart
|
||||
transferData={transferData}
|
||||
timeFrame={TIME_FRAMES[timeFrame]}
|
||||
chains={selectedChains}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<CircularProgress className={classes.alignCenter} />
|
||||
)
|
||||
) : displayBy === "Percent" ? (
|
||||
<VolumeStackedBarChart
|
||||
transferData={transferData}
|
||||
timeFrame={TIME_FRAMES[timeFrame]}
|
||||
selectedChains={selectedChains}
|
||||
/>
|
||||
) : transactionTotals.data ? (
|
||||
allChainsSelected ? (
|
||||
<TransactionsAreaChart
|
||||
transactionData={transactionData}
|
||||
timeFrame={TIME_FRAMES[timeFrame]}
|
||||
/>
|
||||
) : (
|
||||
<TransactionsLineChart
|
||||
transactionData={transactionData}
|
||||
timeFrame={TIME_FRAMES[timeFrame]}
|
||||
chains={selectedChains}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<CircularProgress className={classes.alignCenter} />
|
||||
)}
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default VolumeStats;
|
|
@ -1,259 +1,16 @@
|
|||
import { BigNumber } from "@ethersproject/bignumber";
|
||||
import { formatUnits, parseUnits } from "@ethersproject/units";
|
||||
import {
|
||||
CircularProgress,
|
||||
Container,
|
||||
makeStyles,
|
||||
Paper,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import clsx from "clsx";
|
||||
import numeral from "numeral";
|
||||
import { useMemo } from "react";
|
||||
import useTVL from "../../hooks/useTVL";
|
||||
import { COLORS } from "../../muiTheme";
|
||||
import { Container } from "@material-ui/core";
|
||||
import HeaderText from "../HeaderText";
|
||||
import SmartAddress from "../SmartAddress";
|
||||
import { balancePretty } from "../TokenSelectors/TokenPicker";
|
||||
import CustodyAddresses from "./CustodyAddresses";
|
||||
import NFTStats from "./NFTStats";
|
||||
import MuiReactTable from "./tableComponents/MuiReactTable";
|
||||
import TransactionMetrics from "./TransactionMetrics";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
logoPositioner: {
|
||||
height: "30px",
|
||||
width: "30px",
|
||||
maxWidth: "30px",
|
||||
marginRight: theme.spacing(1),
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
logo: {
|
||||
maxHeight: "100%",
|
||||
maxWidth: "100%",
|
||||
},
|
||||
tokenContainer: {
|
||||
display: "flex",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
},
|
||||
mainPaper: {
|
||||
backgroundColor: COLORS.whiteWithTransparency,
|
||||
padding: "2rem",
|
||||
"& > h, & > p ": {
|
||||
margin: ".5rem",
|
||||
},
|
||||
marginBottom: theme.spacing(8),
|
||||
},
|
||||
flexBox: {
|
||||
display: "flex",
|
||||
alignItems: "flex-end",
|
||||
marginBottom: theme.spacing(4),
|
||||
textAlign: "left",
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
flexDirection: "column",
|
||||
alignItems: "unset",
|
||||
},
|
||||
},
|
||||
grower: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
explainerContainer: {},
|
||||
totalContainer: {
|
||||
display: "flex",
|
||||
alignItems: "flex-end",
|
||||
paddingBottom: 1, // line up with left text bottom
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
marginTop: theme.spacing(1),
|
||||
},
|
||||
},
|
||||
totalValue: {
|
||||
marginLeft: theme.spacing(0.5),
|
||||
marginBottom: "-.125em", // line up number with label
|
||||
},
|
||||
alignCenter: {
|
||||
margin: "0 auto",
|
||||
display: "block",
|
||||
},
|
||||
}));
|
||||
|
||||
const StatsRoot: React.FC<any> = () => {
|
||||
const classes = useStyles();
|
||||
const tvl = useTVL();
|
||||
|
||||
const sortTokens = useMemo(() => {
|
||||
return (rowA: any, rowB: any) => {
|
||||
if (rowA.isGrouped && rowB.isGrouped) {
|
||||
return rowA.values.assetAddress > rowB.values.assetAddress ? 1 : -1;
|
||||
} else if (rowA.isGrouped && !rowB.isGrouped) {
|
||||
return 1;
|
||||
} else if (!rowA.isGrouped && rowB.isGrouped) {
|
||||
return -1;
|
||||
} else if (rowA.original.symbol && !rowB.original.symbol) {
|
||||
return 1;
|
||||
} else if (rowB.original.symbol && !rowA.original.symbol) {
|
||||
return -1;
|
||||
} else if (rowA.original.symbol && rowB.original.symbol) {
|
||||
return rowA.original.symbol > rowB.original.symbol ? 1 : -1;
|
||||
} else {
|
||||
return rowA.original.assetAddress > rowB.original.assetAddress ? 1 : -1;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
const tvlColumns = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
Header: "Token",
|
||||
id: "assetAddress",
|
||||
sortType: sortTokens,
|
||||
disableGroupBy: true,
|
||||
accessor: (value: any) => ({
|
||||
chainId: value.originChainId,
|
||||
symbol: value.symbol,
|
||||
name: value.name,
|
||||
logo: value.logo,
|
||||
assetAddress: value.assetAddress,
|
||||
}),
|
||||
aggregate: (leafValues: any) => leafValues.length,
|
||||
Aggregated: ({ value }: { value: any }) =>
|
||||
`${value} Token${value === 1 ? "" : "s"}`,
|
||||
Cell: (value: any) => (
|
||||
<div className={classes.tokenContainer}>
|
||||
<div className={classes.logoPositioner}>
|
||||
{value.row?.original?.logo ? (
|
||||
<img
|
||||
src={value.row?.original?.logo}
|
||||
alt=""
|
||||
className={classes.logo}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<SmartAddress
|
||||
chainId={value.row?.original?.originChainId}
|
||||
address={value.row?.original?.assetAddress}
|
||||
symbol={value.row?.original?.symbol}
|
||||
tokenName={value.row?.original?.name}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{ Header: "Chain", accessor: "originChain" },
|
||||
{
|
||||
Header: "Amount",
|
||||
accessor: "amount",
|
||||
align: "right",
|
||||
disableGroupBy: true,
|
||||
Cell: (value: any) =>
|
||||
value.row?.original?.amount !== undefined
|
||||
? numeral(value.row?.original?.amount).format("0,0.00")
|
||||
: "",
|
||||
},
|
||||
{
|
||||
Header: "Total Value (USD)",
|
||||
id: "totalValue",
|
||||
accessor: "totalValue",
|
||||
align: "right",
|
||||
disableGroupBy: true,
|
||||
aggregate: (leafValues: any) =>
|
||||
balancePretty(
|
||||
formatUnits(
|
||||
leafValues.reduce(
|
||||
(p: BigNumber, v: number | null | undefined) =>
|
||||
v ? p.add(parseUnits(v.toFixed(18).toString(), 18)) : p,
|
||||
BigNumber.from(0)
|
||||
),
|
||||
18
|
||||
)
|
||||
),
|
||||
Aggregated: ({ value }: { value: any }) => value,
|
||||
Cell: (value: any) =>
|
||||
value.row?.original?.totalValue !== undefined
|
||||
? numeral(value.row?.original?.totalValue).format("0.0 a")
|
||||
: "",
|
||||
},
|
||||
{
|
||||
Header: "Unit Price (USD)",
|
||||
accessor: "quotePrice",
|
||||
align: "right",
|
||||
disableGroupBy: true,
|
||||
Cell: (value: any) =>
|
||||
value.row?.original?.quotePrice !== undefined
|
||||
? numeral(value.row?.original?.quotePrice).format("0,0.00")
|
||||
: "",
|
||||
},
|
||||
];
|
||||
}, [
|
||||
classes.logo,
|
||||
classes.tokenContainer,
|
||||
classes.logoPositioner,
|
||||
sortTokens,
|
||||
]);
|
||||
const tvlString = useMemo(() => {
|
||||
if (!tvl.data) {
|
||||
return "";
|
||||
} else {
|
||||
let sum = 0;
|
||||
tvl.data.forEach((val) => {
|
||||
if (val.totalValue) sum += val.totalValue;
|
||||
});
|
||||
return numeral(sum)
|
||||
.format(sum >= 1000000000 ? "0.000 a" : "0 a")
|
||||
.toUpperCase();
|
||||
}
|
||||
}, [tvl.data]);
|
||||
import TVLStats from "./TVLStats";
|
||||
import VolumeStats from "./VolumeStats";
|
||||
|
||||
const StatsRoot = () => {
|
||||
return (
|
||||
<Container maxWidth="lg">
|
||||
<Container maxWidth="md">
|
||||
<HeaderText white>Rock Hard Stats</HeaderText>
|
||||
<HeaderText white>Stats</HeaderText>
|
||||
</Container>
|
||||
<div className={classes.flexBox}>
|
||||
<div className={classes.explainerContainer}>
|
||||
<Typography variant="h4">Total Value Locked</Typography>
|
||||
<Typography variant="subtitle1" color="textSecondary">
|
||||
These assets are currently locked by the Token Bridge contracts.
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.grower} />
|
||||
{!tvl.isFetching ? (
|
||||
<div
|
||||
className={clsx(classes.explainerContainer, classes.totalContainer)}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="textSecondary"
|
||||
component="div"
|
||||
noWrap
|
||||
>
|
||||
{"Total (USD)"}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h3"
|
||||
component="div"
|
||||
noWrap
|
||||
className={classes.totalValue}
|
||||
>
|
||||
{tvlString}
|
||||
</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<Paper className={classes.mainPaper}>
|
||||
{!tvl.isFetching ? (
|
||||
<MuiReactTable
|
||||
columns={tvlColumns}
|
||||
data={tvl.data || []}
|
||||
skipPageReset={false}
|
||||
initialState={{ sortBy: [{ id: "totalValue", desc: true }] }}
|
||||
/>
|
||||
) : (
|
||||
<CircularProgress className={classes.alignCenter} />
|
||||
)}
|
||||
</Paper>
|
||||
<TransactionMetrics />
|
||||
<CustodyAddresses />
|
||||
<NFTStats />
|
||||
<TVLStats />
|
||||
<VolumeStats />
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import axios from "axios";
|
||||
import {
|
||||
DataWrapper,
|
||||
errorDataWrapper,
|
||||
fetchDataWrapper,
|
||||
receiveDataWrapper,
|
||||
} from "../store/helpers";
|
||||
import { TVL_CUMULATIVE_URL } from "../utils/consts";
|
||||
|
||||
export interface LockedAsset {
|
||||
Symbol: string;
|
||||
Name: string;
|
||||
Address: string;
|
||||
CoinGeckoId: string;
|
||||
Amount: number;
|
||||
Notional: number;
|
||||
TokenPrice: number;
|
||||
}
|
||||
|
||||
export interface LockedAssets {
|
||||
[tokenAddress: string]: LockedAsset;
|
||||
}
|
||||
|
||||
export interface ChainsAssets {
|
||||
[chainId: string]: LockedAssets;
|
||||
}
|
||||
|
||||
export interface NotionalTVLCumulative {
|
||||
DailyLocked: {
|
||||
[date: string]: ChainsAssets;
|
||||
};
|
||||
}
|
||||
|
||||
const useCumulativeTVL = () => {
|
||||
const [cumulativeTVL, setCumulativeTVL] = useState<
|
||||
DataWrapper<NotionalTVLCumulative>
|
||||
>(fetchDataWrapper());
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
axios
|
||||
.get<NotionalTVLCumulative>(TVL_CUMULATIVE_URL)
|
||||
.then((response) => {
|
||||
if (!cancelled) {
|
||||
setCumulativeTVL(receiveDataWrapper(response.data));
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!cancelled) {
|
||||
setCumulativeTVL(errorDataWrapper(error));
|
||||
}
|
||||
console.log(error);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return cumulativeTVL;
|
||||
};
|
||||
|
||||
export default useCumulativeTVL;
|
|
@ -0,0 +1,50 @@
|
|||
import axios from "axios";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
DataWrapper,
|
||||
errorDataWrapper,
|
||||
fetchDataWrapper,
|
||||
receiveDataWrapper,
|
||||
} from "../store/helpers";
|
||||
import { NOTIONAL_TRANSFERRED_URL } from "../utils/consts";
|
||||
|
||||
export interface TransferFromData {
|
||||
[leavingChainId: string]: number;
|
||||
}
|
||||
|
||||
export interface NotionalTransferredFrom {
|
||||
Total: number;
|
||||
Daily: {
|
||||
[date: string]: TransferFromData;
|
||||
};
|
||||
}
|
||||
|
||||
const useNotionalTransferred = () => {
|
||||
const [notionalTransferred, setNotionalTransferred] = useState<
|
||||
DataWrapper<NotionalTransferredFrom>
|
||||
>(fetchDataWrapper());
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
axios
|
||||
.get<NotionalTransferredFrom>(NOTIONAL_TRANSFERRED_URL)
|
||||
.then((response) => {
|
||||
if (!cancelled) {
|
||||
setNotionalTransferred(receiveDataWrapper(response.data));
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!cancelled) {
|
||||
setNotionalTransferred(errorDataWrapper(error));
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return notionalTransferred;
|
||||
};
|
||||
|
||||
export default useNotionalTransferred;
|
|
@ -41,12 +41,12 @@ interface ChainsAssets {
|
|||
[chainId: string]: LockedAssets;
|
||||
}
|
||||
|
||||
interface NotionalTvl {
|
||||
export interface NotionalTVL {
|
||||
Last24HoursChange: ChainsAssets;
|
||||
AllTime: ChainsAssets;
|
||||
}
|
||||
|
||||
const createTVLArray = (notionalTvl: NotionalTvl) => {
|
||||
export const createTVLArray = (notionalTvl: NotionalTVL) => {
|
||||
const tvl: TVL[] = [];
|
||||
for (const [chainId, chainAssets] of Object.entries(notionalTvl.AllTime)) {
|
||||
if (chainId === "*") continue;
|
||||
|
@ -71,16 +71,16 @@ const createTVLArray = (notionalTvl: NotionalTvl) => {
|
|||
return tvl;
|
||||
};
|
||||
|
||||
const useTVL = () => {
|
||||
const [tvl, setTvl] = useState<DataWrapper<TVL[]>>(fetchDataWrapper());
|
||||
export const useTVL = () => {
|
||||
const [tvl, setTvl] = useState<DataWrapper<NotionalTVL>>(fetchDataWrapper());
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
axios
|
||||
.get<NotionalTvl>(TVL_URL)
|
||||
.get<NotionalTVL>(TVL_URL)
|
||||
.then((response) => {
|
||||
if (!cancelled) {
|
||||
setTvl(receiveDataWrapper(createTVLArray(response.data)));
|
||||
setTvl(receiveDataWrapper(response.data));
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
|
@ -6,7 +6,7 @@ import { formatUnits } from "@ethersproject/units";
|
|||
import axios from "axios";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { DataWrapper } from "../store/helpers";
|
||||
import useTVL from "./useTVL";
|
||||
import { createTVLArray, useTVL } from "./useTVL";
|
||||
|
||||
function convertbase64ToBinary(base64: string) {
|
||||
var raw = window.atob(base64);
|
||||
|
@ -24,6 +24,9 @@ function convertbase64ToBinary(base64: string) {
|
|||
//Don't actually mount this hook, it's way to expensive for the prod site.
|
||||
const useTotalTransactedAmount = (): DataWrapper<number> => {
|
||||
const tvl = useTVL();
|
||||
const tvlArray = useMemo(() => {
|
||||
return tvl.data ? createTVLArray(tvl.data) : [];
|
||||
}, [tvl]);
|
||||
const [everyVaaPayloadInHistory, setEveryVaaPayloadInHistory] = useState<
|
||||
{ EmitterChain: string; EmitterAddress: string; Payload: string }[] | null
|
||||
>(null);
|
||||
|
@ -84,7 +87,7 @@ const useTotalTransactedAmount = (): DataWrapper<number> => {
|
|||
const assetAddress =
|
||||
hexToNativeString(payload.originAddress, payload.originChain) || "";
|
||||
|
||||
const tvlItem = tvl.data?.find((item) => {
|
||||
const tvlItem = tvlArray.find((item) => {
|
||||
return (
|
||||
assetAddress &&
|
||||
item.assetAddress.toLowerCase() === assetAddress.toLowerCase()
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import axios from "axios";
|
||||
import {
|
||||
DataWrapper,
|
||||
errorDataWrapper,
|
||||
fetchDataWrapper,
|
||||
receiveDataWrapper,
|
||||
} from "../store/helpers";
|
||||
import { TOTAL_TRANSACTIONS_WORMHOLE } from "../utils/consts";
|
||||
|
||||
export interface Totals {
|
||||
TotalCount: { [chainId: string]: number };
|
||||
DailyTotals: {
|
||||
// "2021-08-22": { "*": 0 },
|
||||
[date: string]: { [groupByKey: string]: number };
|
||||
};
|
||||
}
|
||||
|
||||
const useTransactionTotals = () => {
|
||||
const [totals, setTotals] = useState<DataWrapper<Totals>>(fetchDataWrapper());
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
axios
|
||||
.get<Totals>(TOTAL_TRANSACTIONS_WORMHOLE)
|
||||
.then((response) => {
|
||||
if (!cancelled) {
|
||||
setTotals(receiveDataWrapper(response.data));
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!cancelled) {
|
||||
setTotals(errorDataWrapper(error));
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return totals;
|
||||
};
|
||||
|
||||
export default useTransactionTotals;
|
|
@ -705,6 +705,8 @@ export const COVALENT_GET_TOKENS_URL = (
|
|||
};
|
||||
export const TVL_URL =
|
||||
"https://europe-west3-wormhole-315720.cloudfunctions.net/mainnet-notionaltvl";
|
||||
export const TVL_CUMULATIVE_URL =
|
||||
"https://europe-west3-wormhole-315720.cloudfunctions.net/mainnet-notionaltvlcumulative?totalsOnly=true";
|
||||
export const TERRA_SWAPRATE_URL =
|
||||
"https://fcd.terra.dev/v1/market/swaprate/uusd";
|
||||
|
||||
|
@ -1043,6 +1045,9 @@ export const TOTAL_TRANSACTIONS_WORMHOLE = `https://europe-west3-wormhole-315720
|
|||
|
||||
export const RECENT_TRANSACTIONS_WORMHOLE = `https://europe-west3-wormhole-315720.cloudfunctions.net/mainnet-recent?groupBy=address&numRows=2`;
|
||||
|
||||
export const NOTIONAL_TRANSFERRED_URL =
|
||||
"https://europe-west3-wormhole-315720.cloudfunctions.net/mainnet-notionaltransferredfrom";
|
||||
|
||||
export const VAA_EMITTER_ADDRESSES = [
|
||||
`${CHAIN_ID_SOLANA}:ec7372995d5cc8732397fb0ad35c0121e0eaa90d26f828a534cab54391b3a4f5`, //SOLANA TOKEN
|
||||
`${CHAIN_ID_SOLANA}:0def15a24423e1edd1a5ab16f557b9060303ddbab8c803d2ee48f4b78a1cfd6b`, //SOLAN NFT
|
||||
|
@ -1193,3 +1198,21 @@ export const ACALA_RELAYER_URL =
|
|||
|
||||
export const ACALA_RELAY_URL = `${ACALA_RELAYER_URL}/relay`;
|
||||
export const ACALA_SHOULD_RELAY_URL = `${ACALA_RELAYER_URL}/shouldRelay`;
|
||||
|
||||
export const getChainShortName = (chainId: ChainId) => {
|
||||
return chainId === CHAIN_ID_BSC ? "BSC" : CHAINS_BY_ID[chainId]?.name;
|
||||
};
|
||||
|
||||
export const COLOR_BY_CHAIN_ID: { [key in ChainId]?: string } = {
|
||||
[CHAIN_ID_SOLANA]: "#31D7BB",
|
||||
[CHAIN_ID_ETH]: "#8A92B2",
|
||||
[CHAIN_ID_TERRA]: "#5493F7",
|
||||
[CHAIN_ID_BSC]: "#F0B90B",
|
||||
[CHAIN_ID_POLYGON]: "#8247E5",
|
||||
[CHAIN_ID_AVAX]: "#E84142",
|
||||
[CHAIN_ID_OASIS]: "#0092F6",
|
||||
[CHAIN_ID_AURORA]: "#23685A",
|
||||
[CHAIN_ID_FANTOM]: "#1969FF",
|
||||
[CHAIN_ID_KARURA]: "#FF4B3B",
|
||||
[CHAIN_ID_ACALA]: "#E00F51",
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue