Commit 8ca3d5d3 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into verification-api

parents 1b21831c 6fd2316b
......@@ -44,9 +44,10 @@ NEXT_PUBLIC_AD_ADBUTLER_ON=__PLACEHOLDER_FORNEXT_PUBLIC_AD_ADBUTLER_ON__
# api config
NEXT_PUBLIC_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_HOST__
NEXT_PUBLIC_API_BASE_PATH=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_BASE_PATH__
NEXT_PUBLIC_STATS_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_STATS_API_HOST__
NEXT_PUBLIC_API_PROTOCOL=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_PROTOCOL__
NEXT_PUBLIC_API_PORT=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_PORT__
NEXT_PUBLIC_STATS_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_STATS_API_HOST__
NEXT_PUBLIC_VISUALIZE_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_VISUALIZE_API_HOST__
# external services config
NEXT_PUBLIC_SENTRY_DSN=__PLACEHOLDER_FOR_NEXT_PUBLIC_SENTRY_DSN__
......
......@@ -96,6 +96,7 @@ The app instance could be customized by passing following variables to NodeJS en
| NEXT_PUBLIC_API_HOST | `string` *(optional)* | By default the API endpoint base URL will be set as `https://blockscout.com`. If it is not the case, pass the API host in this variable | `my-host.com` |
| NEXT_PUBLIC_API_BASE_PATH | `string` *(optional)* | Base path for API endpoint url | `/poa/core` |
| NEXT_PUBLIC_STATS_API_HOST | `string` *(optional)* | Pass the Stats API host in this variable | `https://my-host.com` |
| NEXT_PUBLIC_VISUALIZE_API_HOST | `string` *(optional)* | Pass the Visualize API host in this variable | `https://my-host.com` |
### Featured network configuration properties
......
......@@ -108,6 +108,10 @@ const config = Object.freeze({
endpoint: getEnvValue(process.env.NEXT_PUBLIC_STATS_API_HOST),
basePath: '',
},
visualizeApi: {
endpoint: getEnvValue(process.env.NEXT_PUBLIC_VISUALIZE_API_HOST),
basePath: '',
},
homepage: {
charts: parseEnvJson<Array<ChainIndicatorId>>(getEnvValue(process.env.NEXT_PUBLIC_HOMEPAGE_CHARTS)) || [],
plateGradient: getEnvValue(process.env.NEXT_PUBLIC_HOMEPAGE_PLATE_GRADIENT) ||
......
......@@ -13,3 +13,4 @@ NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
# api config
NEXT_PUBLIC_API_HOST=blockscout.com
NEXT_PUBLIC_STATS_API_HOST=https://stats-test.aws-k8s.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=http://94.131.100.174:8050
......@@ -356,13 +356,13 @@ geth:
scVerifier:
enabled: true
image:
_default: ghcr.io/blockscout/smart-contract-verifier:latest
_default: ghcr.io/blockscout/smart-contract-verifier:main
replicas:
app: 1
docker:
port: 80
targetPort: 8043
metricsPort: 6060
# docker:
# port: 80
# targetPort: 8043
# metricsPort: 6060
service:
# ClusterIP, NodePort or LoadBalancer
type: ClusterIP
......@@ -392,10 +392,10 @@ scVerifier:
# probes
livenessProbe:
enabled: true
path: /health
# path: /health
readinessProbe:
enabled: true
path: /health
# path: /health
# enable Horizontal Pod Autoscaler
hpa:
enabled: true
......@@ -403,8 +403,10 @@ scVerifier:
maxReplicas: 10
cpuTarget: 90
environment:
SMART_CONTRACT_VERIFIER__SERVER__ADDR:
_default: 0.0.0.0:8043
SMART_CONTRACT_VERIFIER__SERVER__HTTP__ADDR:
_default: 0.0.0.0:8050
SMART_CONTRACT_VERIFIER__SERVER__GRPC__ADDR:
_default: 0.0.0.0:8051
# SMART_CONTRACT_VERIFIER__SOLIDITY__ENABLED:
# _default: 'true'
SMART_CONTRACT_VERIFIER__SOLIDITY__COMPILERS_DIR:
......@@ -470,11 +472,14 @@ frontend:
- "/tx"
- "/blocks"
- "/block"
# - "/address"
- "/address"
- "/stats"
- "/search-results"
- "/tokens"
- "/token"
- "/accounts"
- "/visualize"
resources:
limits:
memory:
......@@ -542,6 +547,8 @@ frontend:
_default: blockscout.com/eth/goerli
NEXT_PUBLIC_STATS_API_HOST:
_default: https://stats-test.aws-k8s.blockscout.com/
NEXT_PUBLIC_VISUALIZE_API_HOST:
_default: https://visualizer.aws-k8s.blockscout.com
NEXT_PUBLIC_APP_HOST:
_default: blockscout.com/eth/goerli
NEXT_PUBLIC_LOGOUT_URL:
......
......@@ -202,13 +202,13 @@ geth:
scVerifier:
enabled: false
image:
_default: ghcr.io/blockscout/smart-contract-verifier:latest
_default: ghcr.io/blockscout/smart-contract-verifier:main
replicas:
app: 1
docker:
port: 80
targetPort: 8043
metricsPort: 6060
# docker:
# port: 80
# targetPort: 8043
# metricsPort: 6060
service:
# ClusterIP, NodePort or LoadBalancer
type: ClusterIP
......@@ -238,10 +238,10 @@ scVerifier:
# probes
livenessProbe:
enabled: true
path: /health
# path: /health
readinessProbe:
enabled: true
path: /health
# path: /health
# enable Horizontal Pod Autoscaler
hpa:
enabled: true
......@@ -249,8 +249,10 @@ scVerifier:
maxReplicas: 10
cpuTarget: 90
environment:
SMART_CONTRACT_VERIFIER__SERVER__ADDR:
_default: 0.0.0.0:8043
SMART_CONTRACT_VERIFIER__SERVER__HTTP__ADDR:
_default: 0.0.0.0:8050
SMART_CONTRACT_VERIFIER__SERVER__GRPC__ADDR:
_default: 0.0.0.0:8051
# SMART_CONTRACT_VERIFIER__SOLIDITY__ENABLED:
# _default: 'true'
SMART_CONTRACT_VERIFIER__SOLIDITY__COMPILERS_DIR:
......@@ -324,6 +326,7 @@ frontend:
- "/token"
- "/tokens"
- "/accounts"
- "/visualize"
resources:
limits:
......@@ -382,6 +385,8 @@ frontend:
review: blockscout-main.test.aws-k8s.blockscout.com
NEXT_PUBLIC_STATS_API_HOST:
_default: https://stats-test.aws-k8s.blockscout.com/
NEXT_PUBLIC_VISUALIZE_API_HOST:
_default: https://visualizer.aws-k8s.blockscout.com
NEXT_PUBLIC_AUTH_URL:
_default: https://blockscout-main.test.aws-k8s.blockscout.com
NEXT_PUBLIC_API_BASE_PATH:
......
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.4 2.933a.156.156 0 0 0-.155.156v10.844a.467.467 0 0 1-.934 0V3.09A1.089 1.089 0 0 1 6.401 2h7.474a.467.467 0 0 1 .322.137l4.355 4.355a.467.467 0 0 1 .137.33v7.111a.467.467 0 0 1-.933 0V7.29h-3.89a.467.467 0 0 1-.466-.467V2.933h-7Zm7.933.66v2.763h2.763l-2.763-2.763Z" fill="currentColor" stroke="currentColor" stroke-width=".4" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.224 18.185v.628c0 .308-.04.565-.12.771-.067.19-.181.352-.328.465a.794.794 0 0 1-.48.152.8.8 0 0 1-.48-.152 1.026 1.026 0 0 1-.326-.465 2.159 2.159 0 0 1-.12-.77v-.629c0-.31.04-.566.12-.77.068-.19.181-.352.327-.466a.788.788 0 0 1 .48-.155c.18 0 .34.052.479.155.146.113.26.275.329.465.08.205.12.461.12.771Zm.82.625v-.617c0-.454-.07-.844-.21-1.17a1.674 1.674 0 0 0-.602-.758c-.258-.176-.57-.265-.935-.265-.363 0-.675.089-.939.265-.26.173-.47.437-.6.755-.14.326-.21.717-.21 1.173v.616c0 .452.07.841.21 1.17.139.327.339.578.6.754.264.174.576.26.94.26s.676-.086.935-.26c.262-.176.462-.427.601-.753.14-.33.21-.72.21-1.17Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.22 16.056c.31-.206.671-.306 1.077-.306.406 0 .77.1 1.075.308.306.205.545.51.693.868.155.364.229.788.229 1.267v.616c0 .477-.075.901-.23 1.268v.001a1.923 1.923 0 0 1-.691.862c-.307.207-.67.304-1.076.304-.404 0-.766-.097-1.077-.303h-.001a1.923 1.923 0 0 1-.692-.863c-.156-.367-.23-.793-.23-1.268v-.617c0-.48.074-.905.23-1.27a1.9 1.9 0 0 1 .693-.866Zm1.077.194c-.32 0-.583.078-.8.223a1.403 1.403 0 0 0-.509.642l-.001.004c-.123.287-.19.642-.19 1.074v.616c0 .426.066.781.19 1.073.123.287.294.498.51.643.216.143.479.219.8.219.324 0 .586-.077.797-.219.216-.145.387-.355.51-.643.123-.292.19-.647.19-1.073v-.616c0-.429-.067-.784-.19-1.073v-.002a1.425 1.425 0 0 0-.511-.646h-.001c-.21-.144-.472-.222-.795-.222Zm-.33.898a.78.78 0 0 0-.242.35l-.002.007c-.065.167-.103.39-.103.68v.635c-.006.234.03.466.106.68a.78.78 0 0 0 .24.349c.102.07.215.104.325.102h.011c.11.002.223-.031.325-.102a.768.768 0 0 0 .242-.349l.002-.006c.066-.168.103-.392.103-.68v-.629c0-.29-.037-.514-.102-.68l-.003-.007a.767.767 0 0 0-.244-.35.535.535 0 0 0-.329-.104h-.005a.537.537 0 0 0-.324.104Zm.332-.604a1.037 1.037 0 0 0-.63.203l-.007.005a1.276 1.276 0 0 0-.406.575 2.39 2.39 0 0 0-.136.858v.625a2.41 2.41 0 0 0 .134.857v.001c.083.23.223.433.408.578l.01.008c.185.13.4.2.624.197.224.004.44-.066.625-.198l.008-.006c.187-.144.328-.346.41-.576.093-.243.135-.532.135-.858v-.628a2.39 2.39 0 0 0-.135-.858 1.265 1.265 0 0 0-.41-.576l-.004-.003a1.034 1.034 0 0 0-.626-.204Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.4 2.933a.156.156 0 0 0-.155.156v10.844a.467.467 0 0 1-.934 0V3.09A1.089 1.089 0 0 1 6.401 2h7.474a.467.467 0 0 1 .322.137l4.355 4.355a.467.467 0 0 1 .137.33v7.111a.467.467 0 0 1-.933 0V7.29h-3.89a.467.467 0 0 1-.466-.467v-3.89h-7Zm7.933.66v2.763h2.763l-2.763-2.763Z" fill="currentColor" stroke="currentColor" stroke-width=".4" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.224 18.185v.628c0 .308-.04.565-.12.771-.067.19-.181.352-.328.465a.794.794 0 0 1-.48.152.8.8 0 0 1-.48-.152 1.026 1.026 0 0 1-.326-.465 2.159 2.159 0 0 1-.12-.77v-.629c0-.31.04-.566.12-.77.068-.19.181-.352.327-.466a.788.788 0 0 1 .48-.155c.18 0 .34.052.479.155.146.113.26.275.329.465.08.205.12.461.12.771Zm.82.625v-.617c0-.454-.07-.844-.21-1.17a1.674 1.674 0 0 0-.602-.758c-.258-.176-.57-.265-.935-.265-.363 0-.675.089-.939.265a1.65 1.65 0 0 0-.6.755c-.14.326-.21.717-.21 1.173v.616c0 .452.07.841.21 1.17.139.327.339.578.6.754.264.174.576.26.94.26s.676-.086.935-.26c.262-.176.462-.427.601-.753.14-.33.21-.72.21-1.17Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.22 16.056c.31-.206.671-.306 1.077-.306.406 0 .77.1 1.075.308.306.205.545.51.693.868.155.364.229.788.229 1.267v.616c0 .477-.075.901-.23 1.268v.001a1.923 1.923 0 0 1-.691.862c-.307.207-.67.304-1.076.304-.404 0-.766-.097-1.077-.303h-.001a1.923 1.923 0 0 1-.692-.863c-.156-.367-.23-.793-.23-1.268v-.617c0-.48.074-.905.23-1.27a1.9 1.9 0 0 1 .693-.866Zm1.077.194c-.32 0-.583.078-.8.223a1.403 1.403 0 0 0-.509.642l-.001.004c-.123.287-.19.642-.19 1.074v.616c0 .426.066.781.19 1.073.123.287.294.498.51.643.216.143.479.219.8.219.324 0 .586-.077.797-.219.216-.145.387-.355.51-.643.123-.292.19-.647.19-1.073v-.616c0-.429-.067-.784-.19-1.073v-.002a1.425 1.425 0 0 0-.511-.646h-.001c-.21-.144-.472-.222-.795-.222Zm-.33.898a.78.78 0 0 0-.242.35l-.002.007c-.065.167-.103.39-.103.68v.635c-.006.234.03.466.106.68a.78.78 0 0 0 .24.349c.102.07.215.104.325.102h.011a.55.55 0 0 0 .325-.102.768.768 0 0 0 .242-.349l.002-.006c.066-.168.103-.392.103-.68v-.629c0-.29-.037-.514-.102-.68l-.003-.007a.767.767 0 0 0-.244-.35.535.535 0 0 0-.329-.104h-.005a.537.537 0 0 0-.324.104Zm.332-.604a1.037 1.037 0 0 0-.63.203l-.007.005a1.276 1.276 0 0 0-.406.575 2.39 2.39 0 0 0-.136.858v.625a2.41 2.41 0 0 0 .134.857v.001c.083.23.223.433.408.578l.01.008c.185.13.4.2.624.197.224.004.44-.066.625-.198l.008-.006c.187-.144.328-.346.41-.576.093-.243.135-.532.135-.858v-.628a2.39 2.39 0 0 0-.135-.858 1.265 1.265 0 0 0-.41-.576l-.004-.003a1.034 1.034 0 0 0-.626-.204Z" fill="currentColor"/>
<path d="M17.06 21v-1h1.301a.5.5 0 0 1 .5.5v.5H17.06Zm-.699 0a.5.5 0 0 1-.5-.5v-3.9a.6.6 0 0 1 1.199 0V21h-.699Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.861 21v-.5a.5.5 0 0 0-.5-.5H17.06v-3.4a.6.6 0 0 0-1.199 0v3.9a.5.5 0 0 0 .5.5h2.5Zm-1.701-1.1v-3.3a.7.7 0 0 0-1.399 0v3.9a.6.6 0 0 0 .6.6h2.6v-.6a.6.6 0 0 0-.6-.6H17.16Z" fill="currentColor"/>
<path d="M6.468 17.532c0-.192.067-.316.163-.398.104-.09.286-.166.562-.166.332 0 .655.108.921.307a.484.484 0 0 0 .579-.776A2.516 2.516 0 0 0 7.195 16h-.001c-.45 0-.873.125-1.192.399-.328.28-.502.68-.502 1.133 0 .244.067.464.196.651.124.182.29.309.452.401.29.165.657.262.949.34l.053.013c.339.09.589.163.76.267.135.083.17.15.17.264 0 .25-.086.352-.19.419-.138.089-.372.145-.696.145a1.548 1.548 0 0 1-.92-.307.484.484 0 0 0-.58.776c.433.322.958.498 1.498.499h.002c.402 0 .853-.064 1.218-.298.4-.256.636-.677.636-1.234 0-.532-.287-.878-.635-1.09-.31-.189-.699-.292-1.003-.373l-.012-.003c-.344-.091-.597-.16-.772-.26a.39.39 0 0 1-.132-.106c-.013-.018-.026-.045-.026-.104Z" fill="currentColor" stroke="currentColor" stroke-width=".4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.468 17.532c0-.192.067-.316.163-.398.104-.09.286-.166.562-.166.332 0 .655.108.921.307a.484.484 0 0 0 .579-.776A2.516 2.516 0 0 0 7.195 16h-.001c-.45 0-.873.125-1.192.399-.328.28-.502.68-.502 1.133 0 .244.067.464.196.651.124.182.29.309.452.401.29.165.657.262.949.34l.053.013c.339.09.589.163.76.267.135.083.17.15.17.264 0 .25-.086.352-.19.419-.138.089-.372.145-.696.145a1.548 1.548 0 0 1-.92-.307.484.484 0 0 0-.58.776A2.52 2.52 0 0 0 7.192 21h.002c.402 0 .853-.064 1.218-.298.4-.256.636-.677.636-1.234 0-.532-.287-.878-.635-1.09-.31-.189-.699-.292-1.003-.373l-.012-.003c-.344-.091-.597-.16-.772-.26a.39.39 0 0 1-.132-.106c-.013-.018-.026-.045-.026-.104Z" fill="currentColor" stroke="currentColor" stroke-width=".4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.4 2.933a.156.156 0 0 0-.155.156v10.844a.467.467 0 0 1-.934 0V3.09A1.089 1.089 0 0 1 6.401 2h7.474a.467.467 0 0 1 .322.137l4.355 4.355a.467.467 0 0 1 .137.33v7.111a.467.467 0 0 1-.933 0V7.29h-3.89a.467.467 0 0 1-.466-.467V2.933h-7Zm7.933.66v2.763h2.763l-2.763-2.763Z" fill="currentColor" stroke="currentColor" stroke-width=".4" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.4 2.933a.156.156 0 0 0-.155.156v10.844a.467.467 0 0 1-.934 0V3.09A1.089 1.089 0 0 1 6.401 2h7.474a.467.467 0 0 1 .322.137l4.355 4.355a.467.467 0 0 1 .137.33v7.111a.467.467 0 0 1-.933 0V7.29h-3.89a.467.467 0 0 1-.466-.467v-3.89h-7Zm7.933.66v2.763h2.763l-2.763-2.763Z" fill="currentColor" stroke="currentColor" stroke-width=".4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17.06 20h1.301a.5.5 0 0 1 .5.5v.5h-2.5a.5.5 0 0 1-.5-.5v-3.9a.6.6 0 0 1 1.199 0V20Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.861 21v-.5a.5.5 0 0 0-.5-.5H17.06v-3.4a.6.6 0 0 0-1.199 0v3.9a.5.5 0 0 0 .5.5h2.5Zm-1.701-1.1v-3.3a.7.7 0 0 0-1.399 0v3.9a.6.6 0 0 0 .6.6h2.6v-.6a.6.6 0 0 0-.6-.6H17.16Z" fill="currentColor"/>
<path d="M11.18 15.85a.63.63 0 0 1 .63.63v3.131c0 .177.043.291.11.365.066.071.156.113.29.115.135-.002.222-.044.282-.113l.002-.002c.067-.074.11-.188.11-.364V16.48a.63.63 0 0 1 1.26 0v3.288h-.007a1.44 1.44 0 0 1-.213.641l-.001.002a1.592 1.592 0 0 1-.592.543l-.002.001a1.74 1.74 0 0 1-.682.189v.006h-.313v-.006a1.788 1.788 0 0 1-.688-.188l-.003-.002a1.592 1.592 0 0 1-.592-.543v-.002a1.44 1.44 0 0 1-.214-.64h-.007v-3.29a.63.63 0 0 1 .63-.629ZM7.286 16.285l.397 1.229.484-1.3a.558.558 0 0 1 1.046.388l-.983 2.656v1.304a.588.588 0 1 1-1.177 0v-1.304l-.953-2.56a.628.628 0 1 1 1.186-.413Z" fill="currentColor"/>
<path d="M11.18 15.85a.63.63 0 0 1 .63.63v3.131c0 .177.043.291.11.365a.378.378 0 0 0 .29.115c.135-.002.222-.044.282-.113l.002-.002c.067-.074.11-.188.11-.364V16.48a.63.63 0 0 1 1.26 0v3.288h-.007a1.44 1.44 0 0 1-.213.641l-.001.002a1.592 1.592 0 0 1-.592.543l-.002.001a1.74 1.74 0 0 1-.682.189v.006h-.313v-.006a1.788 1.788 0 0 1-.688-.188l-.003-.002a1.592 1.592 0 0 1-.592-.543v-.002a1.44 1.44 0 0 1-.214-.64h-.007v-3.29a.63.63 0 0 1 .63-.629Zm-3.894.435.397 1.229.484-1.3a.558.558 0 0 1 1.046.388l-.983 2.656v1.304a.588.588 0 1 1-1.177 0v-1.304l-.953-2.56a.628.628 0 1 1 1.186-.413Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 33 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.346 13.736c.607 1.073-.75 1.406-1.386 1.467-.965.094-1.165-.464-.976-1.166a1.262 1.262 0 0 1 1.085-.951 1.342 1.342 0 0 1 1.277.65Zm3.787-1.984c-.705 5.417 8.8 4.29 8.618 8.6.938-1.223 1.34-4.55-1.42-6.262-2.458-1.526-5.662-.69-7.198-2.338Zm5.474-2.042c-.061-.058-.125-.114-.187-.17.063.057.125.117.187.17Z" fill="currentColor"/>
<path d="m25.817 13.658-.006-.009a3.601 3.601 0 0 0-.292-.458 2.358 2.358 0 0 0-1.303-.886 5.545 5.545 0 0 0-1.06-.167c-.364-.027-.734-.042-1.108-.061-.75-.042-1.518-.12-2.268-.334a6.304 6.304 0 0 1-1.11-.425 4.745 4.745 0 0 1-.973-.71c-.577-.54-1.031-1.151-1.486-1.745a20.606 20.606 0 0 0-1.378-1.714 6.285 6.285 0 0 0-1.687-1.324 5.812 5.812 0 0 0-2.101-.602 4.947 4.947 0 0 1 2.244.275 5.934 5.934 0 0 1 1.947 1.242c.367.345.712.714 1.032 1.104 2.38-.47 4.312-.052 5.796.76l.034.016a7.31 7.31 0 0 1 1.507 1.092c.316.292.61.606.88.942l.021.027c.877 1.115 1.31 2.274 1.31 2.977Z" fill="currentColor"/>
<path d="m25.816 13.658-.005-.011.005.01ZM11.165 5.92c.608.088 1.228.331 1.627.795.4.463.546 1.066.662 1.64.093.444.168.897.342 1.318.084.205.208.385.311.579.086.161.241.306.301.478a.154.154 0 0 1-.018.153c-.212.235-.783-.026-1-.132a3.254 3.254 0 0 1-.982-.753c-.866-.966-1.313-2.355-1.286-3.62a4.22 4.22 0 0 1 .043-.458Zm10.161 10.887c-1.312 3.679 4.64 6.147 2.41 9.888 2.289-.95 3.375-3.817 2.425-6.092-.83-1.998-3.286-2.726-4.835-3.796Zm-7.873 4.809c.358-.27.749-.493 1.164-.663.42-.17.854-.295 1.299-.377.882-.169 1.755-.21 2.488-.507.362-.142.695-.35.983-.612.279-.26.492-.583.623-.942.133-.378.188-.78.16-1.181a4.348 4.348 0 0 0-.289-1.256 2.883 2.883 0 0 1 .734 2.614 2.71 2.71 0 0 1-.715 1.294c-.35.346-.77.61-1.234.773-.441.155-.902.25-1.37.282-.45.038-.884.048-1.312.074a8.433 8.433 0 0 0-2.53.502Zm8.397 6.469c-.133.105-.265.217-.41.315-.147.097-.3.183-.459.257-.33.162-.695.245-1.063.242-.997-.02-1.702-.765-2.115-1.608-.281-.574-.475-1.195-.809-1.742-.477-.783-1.294-1.413-2.25-1.296-.39.049-.755.225-.972.565-.57.888.248 2.132 1.292 1.956a1.14 1.14 0 0 0 .259-.072.936.936 0 0 0 .23-.14 1.05 1.05 0 0 0 .317-.461c.069-.188.084-.391.044-.587a.806.806 0 0 0-.335-.502c.2.094.356.263.435.47.082.215.104.447.061.672a1.332 1.332 0 0 1-.298.635 1.304 1.304 0 0 1-.281.24 1.638 1.638 0 0 1-1.064.234 1.946 1.946 0 0 1-.947-.413c-.322-.256-.562-.591-.854-.88a4.057 4.057 0 0 0-1.164-.854c-.3-.132-.615-.23-.938-.291a6.176 6.176 0 0 0-.49-.08c-.075-.007-.443-.089-.494-.041a10.29 10.29 0 0 1 1.65-1.243 8.013 8.013 0 0 1 1.935-.832 5.497 5.497 0 0 1 2.164-.166c.374.045.74.14 1.088.282.365.147.702.356.995.618.291.275.526.604.692.97.15.34.262.698.334 1.064.214 1.095.135 2.793 1.563 3.044.074.014.15.025.225.032l.233.006c.16-.012.32-.035.477-.07.326-.076.644-.185.948-.324Z" fill="currentColor"/>
<path d="m13.553 26.89-.037-.029.037.03Zm-1.281-15.456a1.59 1.59 0 0 1-.267.554 2.167 2.167 0 0 1-.889.683c-.315.137-.65.225-.99.262-.075.01-.152.015-.226.02a1.01 1.01 0 0 0-.94.754 2.57 2.57 0 0 0-.057.317c-.034.277-.04.565-.07.914a5.428 5.428 0 0 1-.505 1.706c-.34.718-.72 1.296-.632 2.123.058.537.332.896.696 1.268.656.674 2.125.975 1.797 2.637-.198.991-1.835 2.032-4.135 2.395.229-.035-.294-.919-.326-.975-.247-.388-.517-.754-.713-1.175-.384-.816-.562-1.76-.405-2.655.166-.942.86-1.664 1.436-2.383.687-.856 1.407-1.978 1.566-3.089.037-.27.064-.606.124-.942.057-.371.173-.731.343-1.066.116-.22.269-.417.452-.585a.585.585 0 0 0 .112-.712L4.976 4.86l5.267 6.53a.656.656 0 0 0 1.01.022.448.448 0 0 0 .013-.565c-.344-.442-.708-.892-1.06-1.335L8.88 7.864l-2.66-3.29L2.592 0 6.56 4.278l2.832 3.146L10.806 9c.469.53.937 1.044 1.406 1.601l.077.094.017.146a1.74 1.74 0 0 1-.034.593Zm1.214 15.413a3.535 3.535 0 0 1-.674-.689c.207.247.432.477.674.689Z" fill="currentColor"/>
</svg>
......@@ -29,6 +29,7 @@ import type { TokensResponse, TokensFilters } from 'types/api/tokens';
import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer';
import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction } from 'types/api/transaction';
import type { TTxsFilters } from 'types/api/txsFilters';
import type { VisualizedContract } from 'types/api/visualization';
import type ArrayElement from 'types/utils/ArrayElement';
import appConfig from 'configs/app/config';
......@@ -83,6 +84,13 @@ export const RESOURCES = {
basePath: appConfig.statsApi.basePath,
},
// VISUALIZATION
visualize_sol2uml: {
path: '/api/v1/solidity\\:visualize-contracts',
endpoint: appConfig.visualizeApi.endpoint,
basePath: appConfig.visualizeApi.basePath,
},
// BLOCKS, TXS
blocks: {
path: '/api/v2/blocks',
......@@ -362,6 +370,7 @@ Q extends 'contract_methods_read' ? Array<SmartContractReadMethod> :
Q extends 'contract_methods_read_proxy' ? Array<SmartContractReadMethod> :
Q extends 'contract_methods_write' ? Array<SmartContractWriteMethod> :
Q extends 'contract_methods_write_proxy' ? Array<SmartContractWriteMethod> :
Q extends 'visualize_sol2uml' ? VisualizedContract :
Q extends 'contract_verification_config' ? SmartContractVerificationConfig :
never;
/* eslint-enable @typescript-eslint/indent */
......
......@@ -10,6 +10,8 @@ export const MINUTE = 60 * SECOND;
export const HOUR = 60 * MINUTE;
export const DAY = 24 * HOUR;
export const WEEK = 7 * DAY;
export const MONTH = 30 * DAY;
export const YEAR = 365 * DAY;
export const Kb = 1_000;
export const Mb = 1_000 * Kb;
// some tokens could have symbols like "ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY"
// so in some cases we trim it to max 10 symbols
export default function trimTokenSymbol(symbol: string | null) {
if (!symbol) {
return '';
}
if (symbol.length <= 7) {
return symbol;
}
return symbol.slice(0, 7) + '...';
}
......@@ -26,6 +26,12 @@ export const erc20d: AddressTokenBalance = {
value: '39000000000000000000',
};
export const erc20LongSymbol: AddressTokenBalance = {
token: tokens.tokenInfoERC20LongSymbol,
token_id: null,
value: '39000000000000000000',
};
export const erc721a: AddressTokenBalance = {
token: tokens.tokenInfoERC721a,
token_id: null,
......@@ -44,6 +50,12 @@ export const erc721c: AddressTokenBalance = {
value: '5',
};
export const erc721LongSymbol: AddressTokenBalance = {
token: tokens.tokenInfoERC721LongSymbol,
token_id: null,
value: '5',
};
export const erc1155a: AddressTokenBalance = {
token: tokens.tokenInfoERC1155a,
token_id: '42',
......@@ -62,6 +74,12 @@ export const erc1155withoutName: AddressTokenBalance = {
value: '42',
};
export const erc1155LongId: AddressTokenBalance = {
token: tokens.tokenInfoERC1155b,
token_id: '483200961027732618117991942553110860267520',
value: '42',
};
export const baseList = [
erc20a,
erc20b,
......@@ -73,3 +91,9 @@ export const baseList = [
erc1155a,
erc1155b,
];
export const longValuesList = [
erc20LongSymbol,
erc721LongSymbol,
erc1155LongId,
];
......@@ -32,7 +32,7 @@ export const withMultiplePaths: Partial<SmartContract> = {
file_path: './simple_storage.sol',
additional_sources: [
{
file_path: 'contracts/1_Storage.sol',
file_path: '/contracts/protocol/libraries/logic/GenericLogic.sol',
source_code: '// SPDX-License-Identifier: GPL-3.0 \n pragma solidity >=0.7.0 <0.9.0; \n contract Storage {\n //2112313123; \nuint256 number; \n function store(uint256 num) public {\nnumber = num;\n}\n function retrieve() public view returns (uint256)\n {\nreturn number;\n}\n}',
},
],
......
......@@ -12,7 +12,7 @@ export const read: Array<SmartContractReadMethod> = [
{ internalType: 'address', name: '', type: 'address' },
],
method_id: '70a08231',
name: 'balanceOf',
name: 'FLASHLOAN_PREMIUM_TOTAL',
outputs: [
{ internalType: 'uint256', name: '', type: 'uint256', value: '' },
],
......@@ -105,7 +105,7 @@ export const write: Array<SmartContractWriteMethod> = [
{ internalType: 'address', name: 'guy', type: 'address' },
{ internalType: 'uint256', name: 'wad', type: 'uint256' },
],
name: 'approve',
name: 'setReserveInterestRateStrategyAddress',
outputs: [
{ internalType: 'bool', name: '', type: 'bool' },
],
......
......@@ -60,6 +60,17 @@ export const tokenInfoERC20d: TokenInfo = {
type: 'ERC-20',
};
export const tokenInfoERC20LongSymbol: TokenInfo = {
address: '0xCc7bb2D219A0FC08033E130629C2B854b7bA9195',
decimals: '18',
exchange_rate: '1328.89',
holders: '102625',
name: 'Zeta',
symbol: 'ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY',
total_supply: '2100000000000000000000000000',
type: 'ERC-20',
};
export const tokenInfoERC721a: TokenInfo = {
address: '0xDe7cAc71E072FCBd4453E5FB3558C2684d1F88A0',
decimals: null,
......@@ -93,6 +104,17 @@ export const tokenInfoERC721c: TokenInfo = {
type: 'ERC-721',
};
export const tokenInfoERC721LongSymbol: TokenInfo = {
address: '0x47646F1d7dc4Dd2Db5a41D092e2Cf966e27A4992',
decimals: null,
exchange_rate: null,
holders: '12',
name: 'Puma',
symbol: 'ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY',
total_supply: null,
type: 'ERC-721',
};
export const tokenInfoERC1155a: TokenInfo = {
address: '0x4b333DEd10c7ca855EA2C8D4D90A0a8b73788c8e',
decimals: null,
......
......@@ -132,12 +132,13 @@ export const erc1155multiple: TokenTransfer = {
...erc1155,
token: {
...erc1155.token,
name: 'OLYMPIC',
name: 'SastanaNFT',
symbol: 'ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY',
},
total: [
{ token_id: '12345678', value: '100000000000000000000', decimals: null },
{ token_id: '483200961027732618117991942553110860267520', value: '200000000000000000000', decimals: null },
{ token_id: '456', value: '42', decimals: null },
{ token_id: '12345678', value: '142', decimals: null },
{ token_id: '1000006457499', value: '11', decimals: null },
],
};
......
......@@ -64,6 +64,7 @@ export const base: Transaction = {
],
type: 2,
value: '42000000000000000000',
actions: [],
};
export const withContractCreation: Transaction = {
......@@ -170,3 +171,72 @@ export const pending: Transaction = {
type: null,
value: '0',
};
export const withActionsUniswap: Transaction = {
...base,
actions: [
{
data: {
address0: '0x6f16598F00eDabEA92B4Cef4b6aa0d45c898A9AE',
address1: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6',
amount0: '7143.488560357232097378',
amount1: '10',
symbol0: 'XFT',
symbol1: 'Ether',
},
protocol: 'uniswap_v3',
type: 'mint',
},
{
data: {
address: '0xC36442b4a4522E871399CD717aBDD847Ab11FE88',
ids: [
'53699',
'53700123456',
'42',
],
name: 'Uniswap V3: Positions NFT',
symbol: 'UNI-V3-POS',
to: '0x6d872Fb5F5B2B1f71fA9AadE159bc3976c1946B7',
},
protocol: 'uniswap_v3',
type: 'mint_nft',
},
{
data: {
address0: '0x6f16598F00eDabEA92B4Cef4b6aa0d45c898A9AE',
address1: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6',
amount0: '42876.488560357232',
amount1: '345.908098203434',
symbol0: 'SHAVUHA',
symbol1: 'BOB',
},
protocol: 'uniswap_v3',
type: 'swap',
},
{
data: {
address0: '0x6f16598F00eDabEA92B4Cef4b6aa0d45c898A9AE',
address1: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6',
amount0: '42',
amount1: '0.523523223232',
symbol0: 'VIC',
symbol1: 'USDT',
},
protocol: 'uniswap_v3',
type: 'burn',
},
{
data: {
address0: '0x6f16598F00eDabEA92B4Cef4b6aa0d45c898A9AE',
address1: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6',
amount0: '42',
amount1: '0.523523223232',
symbol0: 'BOB',
symbol1: 'UNI',
},
protocol: 'uniswap_v3',
type: 'collect',
},
],
};
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import Sol2Uml from 'ui/pages/Sol2Uml';
const Sol2UmlPage: NextPage = () => {
const title = getNetworkTitle();
return (
<>
<Head>
<title>{ title }</title>
</Head>
<Sol2Uml/>
</>
);
};
export default Sol2UmlPage;
export { getServerSideProps } from 'lib/next/getServerSideProps';
......@@ -3,6 +3,7 @@ import type { BlockTransactionsResponse } from './block';
import type { DecodedInput } from './decodedInput';
import type { Fee } from './fee';
import type { TokenTransfer } from './tokenTransfer';
import type { TxAction } from './txAction';
export type TransactionRevertReason = {
raw: string;
......@@ -48,6 +49,7 @@ export type Transaction = (
method: string | null;
tx_types: Array<TransactionType>;
tx_tag: string | null;
actions: Array<TxAction>;
}
export type TransactionsResponse = TransactionsResponseValidated | TransactionsResponsePending;
......
export interface TxActionGeneral {
type: 'mint' | 'burn' | 'collect' | 'swap';
data: {
amount0: string;
symbol0: string;
address0: string;
amount1: string;
symbol1: string;
address1: string;
};
}
export interface TxActionNft {
type: 'mint_nft';
data: {
name: string;
symbol: string;
address: string;
to: string;
ids: Array<string>;
};
}
export type TxAction = {
protocol: 'uniswap_v3';
} & (TxActionGeneral | TxActionNft)
export interface VisualizedContract {
png: string | null;
svg: string | null;
}
......@@ -97,7 +97,7 @@ const AddressBlocksValidated = ({ scrollRef }: Props) => {
<>
<Hide below="lg" ssr={ false }>
<Table variant="simple" size="sm">
<Thead top={ 80 }>
<Thead top={ query.isPaginationVisible ? 80 : 0 }>
<Tr>
<Th width="17%">Block</Th>
<Th width="17%">Age</Th>
......
import { chakra, Icon, Link, Tooltip } from '@chakra-ui/react';
import { chakra, Icon, Link, Tooltip, Hide } from '@chakra-ui/react';
import React from 'react';
import svgFileIcon from 'icons/files/csv.svg';
......@@ -23,7 +23,7 @@ const AddressCsvExportLink = ({ className, address, type }: Props) => {
href={ link('csv_export', undefined, { type, address }) }
>
<Icon as={ svgFileIcon } boxSize={{ base: '30px', lg: 6 }}/>
{ !isMobile && <chakra.span ml={ 1 }>Download CSV</chakra.span> }
<Hide ssr={ false } below="lg"><chakra.span ml={ 1 }>Download CSV</chakra.span></Hide>
</Link>
</Tooltip>
);
......
......@@ -136,12 +136,14 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
hint="Implementation address of the proxy contract."
columnGap={ 1 }
>
<LinkInternal href={ link('address_index', { id: data.implementation_address }) }>
{ data.implementation_name }
<LinkInternal href={ link('address_index', { id: data.implementation_address }) } overflow="hidden">
{ data.implementation_name || <HashStringShortenDynamic hash={ data.implementation_address }/> }
</LinkInternal>
<Text variant="secondary" overflow="hidden">
<HashStringShortenDynamic hash={ `(${ data.implementation_address })` }/>
</Text>
{ data.implementation_name && (
<Text variant="secondary" overflow="hidden">
<HashStringShortenDynamic hash={ `(${ data.implementation_address })` }/>
</Text>
) }
</DetailsInfoItem>
) }
<AddressBalance data={ data }/>
......
......@@ -196,7 +196,7 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD
data={ items }
baseAddress={ currentAddress }
showTxInfo
top={ 80 }
top={ isActionBarHidden ? 0 : 80 }
enableTimeIncrement
showSocketInfo={ pagination.page === 1 && !tokenFilter }
socketInfoAlert={ socketAlert }
......
......@@ -47,7 +47,7 @@ const AddressCoinBalanceHistory = ({ query }: Props) => {
<>
<Hide below="lg" ssr={ false }>
<Table variant="simple" size="sm">
<Thead top={ 80 }>
<Thead top={ query.isPaginationVisible ? 80 : 0 }>
<Tr>
<Th width="20%">Block</Th>
<Th width="20%">Txn</Th>
......
......@@ -31,7 +31,7 @@ test('verified with changed byte code +@mobile +@dark-mode', async({ mount, page
await expect(component).toHaveScreenshot();
});
test('verified with multiple sources', async({ mount, page }) => {
test('verified with multiple sources +@mobile', async({ mount, page }) => {
await page.route(CONTRACT_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(contractMock.withMultiplePaths),
......
......@@ -39,9 +39,9 @@ const ContractMethodsAccordion = <T extends SmartContractMethod>({ data, renderC
<Accordion allowMultiple position="relative" onChange={ handleAccordionStateChange } index={ expandedSections }>
{ data.map((item, index) => {
return (
<AccordionItem key={ index } as="section" _first={{ borderTopWidth: '0' }}>
<AccordionItem key={ index } as="section" _first={{ borderTopWidth: '0', '.chakra-accordion__button': { pr: '150px' } }}>
<h2>
<AccordionButton px={ 0 } py={ 3 } _hover={{ bgColor: 'inherit' }}>
<AccordionButton px={ 0 } py={ 3 } _hover={{ bgColor: 'inherit' }} wordBreak="break-all" textAlign="left">
<Box as="span" fontFamily="heading" fontWeight={ 500 } fontSize="lg" mr={ 1 }>
{ index + 1 }. { item.type === 'fallback' || item.type === 'receive' ? item.type : item.name }
</Box>
......@@ -82,10 +82,12 @@ const ContractMethodsAccordion = <T extends SmartContractMethod>({ data, renderC
</AccordionItem>
);
}) }
<Flex columnGap={ 3 } position="absolute" top={ 0 } right={ 0 } py={ 3 } lineHeight="27px">
<Link onClick={ handleExpandAll }>{ expandedSections.length === data.length ? 'Collapse' : 'Expand' } all</Link>
<Link onClick={ handleReset }>Reset</Link>
</Flex>
{ data.length > 0 && (
<Flex columnGap={ 3 } position="absolute" top={ 0 } right={ 0 } py={ 3 } lineHeight="27px">
<Link onClick={ handleExpandAll }>{ expandedSections.length === data.length ? 'Collapse' : 'Expand' } all</Link>
<Link onClick={ handleReset }>Reset</Link>
</Flex>
) }
</Accordion>
);
};
......
......@@ -66,7 +66,7 @@ test('error result', async({ mount, page }) => {
await component.getByPlaceholder(/address/i).type('address-hash');
await component.getByText(/query/i).click();
const section = page.locator('section', { hasText: 'balanceOf' });
const section = page.locator('section', { hasText: 'FLASHLOAN_PREMIUM_TOTAL' });
await expect(section).toHaveScreenshot();
});
import { Box, chakra, Flex, Link, Text, Tooltip } from '@chakra-ui/react';
import { Box, chakra, Flex, Text, Tooltip } from '@chakra-ui/react';
import React from 'react';
import type { SmartContract } from 'types/api/contract';
......@@ -6,6 +6,7 @@ import type { SmartContract } from 'types/api/contract';
import link from 'lib/link/link';
import CodeEditor from 'ui/shared/CodeEditor';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import LinkInternal from 'ui/shared/LinkInternal';
interface Props {
data: string;
......@@ -26,13 +27,13 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi
const diagramLink = hasSol2Yml && address ? (
<Tooltip label="Visualize contract code using Sol2Uml JS library">
<Link
<LinkInternal
href={ link('visualize_sol2uml', undefined, { address }) }
ml="auto"
mr={ 3 }
>
View UML diagram
</Link>
</LinkInternal>
</Tooltip>
) : null;
......@@ -58,9 +59,11 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi
<Flex flexDir="column" rowGap={ 3 }>
{ [ { file_path: filePath, source_code: data }, ...additionalSource ].map((item, index, array) => (
<Box key={ index }>
<Flex justifyContent="space-between" alignItems="center" mb={ 3 }>
<chakra.span fontSize="sm">File { index + 1 } of { array.length }: { item.file_path }</chakra.span>
<CopyToClipboard text={ item.source_code }/>
<Flex justifyContent="space-between" alignItems="flex-end" mb={ 3 }>
<chakra.span fontSize="sm" wordBreak="break-all" lineHeight="20px">
File { index + 1 } of { array.length }: { item.file_path }
</chakra.span>
<CopyToClipboard text={ item.source_code } ml={ 4 }/>
</Flex>
<CodeEditor value={ item.source_code } id={ `source_code_${ index }` }/>
</Box>
......
......@@ -3,6 +3,7 @@ import React from 'react';
import type { Address } from 'types/api/address';
import link from 'lib/link/link';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import LinkInternal from 'ui/shared/LinkInternal';
......@@ -12,13 +13,14 @@ interface Props {
const AddressNameInfo = ({ data }: Props) => {
if (data.token) {
const symbol = data.token.symbol ? ` (${ trimTokenSymbol(data.token.symbol) })` : '';
return (
<DetailsInfoItem
title="Token name"
hint="Token name and symbol"
>
<LinkInternal href={ link('token_index', { hash: data.token.address }) }>
{ data.token.name } ({ data.token.symbol })
{ data.token.name }{ symbol }
</LinkInternal>
</DetailsInfoItem>
);
......
......@@ -126,6 +126,37 @@ test('filter', async({ mount, page }) => {
await expect(page).toHaveScreenshot({ clip: CLIPPING_AREA });
});
base('long values', async({ mount, page }) => {
await page.route(ASSET_URL, (route) => {
return route.fulfill({
status: 200,
path: './playwright/image_s.jpg',
});
});
await page.route(ADDRESS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify({ hash: '1' }),
}), { times: 1 });
await page.route(TOKENS_API_URL, async(route) => route.fulfill({
status: 200,
body: JSON.stringify(tokenBalanceMock.longValuesList),
}), { times: 1 });
await mount(
<TestApp>
<MockAddressPage>
<Flex>
<TokenSelect/>
</Flex>
</MockAddressPage>
</TestApp>,
{ hooksConfig },
);
await page.getByRole('button', { name: /select/i }).click();
await expect(page).toHaveScreenshot({ clip: CLIPPING_AREA });
});
test.describe('socket', () => {
const testWithSocket = test.extend<socketServer.SocketServerFixture>({
createSocket: socketServer.createSocket,
......
import { Flex, Text, useColorModeValue } from '@chakra-ui/react';
import { chakra, Flex, Text, useColorModeValue } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import link from 'lib/link/link';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import HashStringShorten from 'ui/shared/HashStringShorten';
import TokenLogo from 'ui/shared/TokenLogo';
......@@ -20,19 +21,23 @@ const TokenSelectItem = ({ data }: Props) => {
const tokenDecimals = Number(data.token.decimals) || 18;
return (
<>
<Text >{ BigNumber(data.value).dividedBy(10 ** tokenDecimals).toFormat(2) } { data.token.symbol }</Text>
{ data.token.exchange_rate && <Text >@{ data.token.exchange_rate }</Text> }
<span >{ BigNumber(data.value).dividedBy(10 ** tokenDecimals).toFormat(2) } { trimTokenSymbol(data.token.symbol) }</span>
{ data.token.exchange_rate && <span >@{ data.token.exchange_rate }</span> }
</>
);
}
case 'ERC-721': {
return <Text >{ BigNumber(data.value).toFormat() } { data.token.symbol }</Text>;
return <chakra.span textOverflow="ellipsis" overflow="hidden">{ BigNumber(data.value).toFormat() } { data.token.symbol }</chakra.span>;
}
case 'ERC-1155': {
return (
<>
<Text >#{ data.token_id || 0 }</Text>
<Text >{ BigNumber(data.value).toFormat() }</Text>
<chakra.span textOverflow="ellipsis" overflow="hidden" mr={ 6 }>
#{ data.token_id || 0 }
</chakra.span>
<span>
{ BigNumber(data.value).toFormat() }
</span>
</>
);
}
......@@ -64,7 +69,7 @@ const TokenSelectItem = ({ data }: Props) => {
<Text fontWeight={ 700 } ml={ 2 }>{ data.token.name || <HashStringShorten hash={ data.token.address }/> }</Text>
{ data.usd && <Text fontWeight={ 700 } ml="auto">${ data.usd.toFormat(2) }</Text> }
</Flex>
<Flex alignItems="center" justifyContent="space-between" w="100%">
<Flex alignItems="center" justifyContent="space-between" w="100%" whiteSpace="nowrap">
{ secondRow }
</Flex>
</Flex>
......
......@@ -12,13 +12,14 @@ interface Props {
items: Array<AddressesItem>;
totalSupply: string;
pageStartIndex: number;
top: number;
}
const AddressesTable = ({ items, totalSupply, pageStartIndex }: Props) => {
const AddressesTable = ({ items, totalSupply, pageStartIndex, top }: Props) => {
const hasPercentage = Boolean(totalSupply && totalSupply !== '0');
return (
<Table variant="simple" size="sm">
<Thead top={ 80 }>
<Thead top={ top }>
<Tr>
<Th width="64px">Rank</Th>
<Th width={ hasPercentage ? '30%' : '40%' }>Address</Th>
......
......@@ -101,8 +101,12 @@ const BlocksContent = ({ type, query }: Props) => {
return (
<>
{ socketAlert && <Alert status="warning" mb={ 6 } as="a" href={ window.document.location.href }>{ socketAlert }</Alert> }
<Show below="lg" key="content-mobile" ssr={ false }><BlocksList data={ query.data.items }/></Show>
<Hide below="lg" key="content-desktop" ssr={ false }><BlocksTable data={ query.data.items } top={ 80 } page={ 1 }/></Hide>
<Show below="lg" key="content-mobile" ssr={ false }>
<BlocksList data={ query.data.items }/>
</Show>
<Hide below="lg" key="content-desktop" ssr={ false }>
<BlocksTable data={ query.data.items } top={ query.isPaginationVisible ? 80 : 0 } page={ query.pagination.page }/>
</Hide>
</>
);
......
......@@ -53,7 +53,13 @@ const BlocksListItem = ({ data, isPending, enableTimeIncrement }: Props) => {
</Flex>
<Flex columnGap={ 2 }>
<Text fontWeight={ 500 }>Txn</Text>
<Text variant="secondary">{ data.tx_count }</Text>
{ data.tx_count > 0 ? (
<LinkInternal href={ link('block', { id: String(data.height) }, { tab: 'txs' }) }>
{ data.tx_count }
</LinkInternal>
) :
<Text variant="secondary">{ data.tx_count }</Text>
}
</Flex>
<Box>
<Text fontWeight={ 500 }>Gas used</Text>
......
......@@ -53,7 +53,13 @@ const BlocksTableItem = ({ data, isPending, enableTimeIncrement }: Props) => {
<Td fontSize="sm">
<AddressLink type="address" alias={ data.miner.name } hash={ data.miner.hash } truncation="constant" display="inline-flex" maxW="100%"/>
</Td>
<Td isNumeric fontSize="sm">{ data.tx_count }</Td>
<Td isNumeric fontSize="sm">
{ data.tx_count > 0 ? (
<LinkInternal href={ link('block', { id: String(data.height) }, { tab: 'txs' }) }>
{ data.tx_count }
</LinkInternal>
) : data.tx_count }
</Td>
<Td fontSize="sm">
<Box>{ BigNumber(data.gas_used || 0).toFormat() }</Box>
<Flex mt={ 2 }>
......
......@@ -130,7 +130,7 @@ const EthereumChart = () => {
type="left"
scale={ yScale }
ticks={ 5 }
tickFormat={ yTickFormat }
tickFormatGenerator={ yTickFormat }
disableAnimation
/>
<ChartOverlay ref={ overlayRef } width={ innerWidth } height={ innerHeight }>
......
......@@ -47,6 +47,7 @@ const Accounts = () => {
{ bar }
<Hide below="lg" ssr={ false }>
<AddressesTable
top={ isPaginationVisible ? 80 : 0 }
items={ data.items }
totalSupply={ data.total_supply }
pageStartIndex={ pageStartIndex }
......
import { Flex } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import Sol2UmlDiagram from 'ui/sol2uml/Sol2UmlDiagram';
const Sol2Uml = () => {
const router = useRouter();
const isMobile = useIsMobile();
const addressHash = router.query.address?.toString() || '';
return (
<Page>
<PageTitle text="Solidity UML diagram"/>
<Flex mb={ 10 }>
<span>For contract</span>
<Address ml={ 3 }>
<AddressIcon address={{ hash: addressHash, is_contract: true, implementation_name: null }}/>
<AddressLink hash={ addressHash } type="address" ml={ 2 } truncation={ isMobile ? 'constant' : 'dynamic' }/>
</Address>
</Flex>
<Sol2UmlDiagram addressHash={ addressHash }/>
</Page>
);
};
export default Sol2Uml;
......@@ -8,6 +8,7 @@ import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import AdBanner from 'ui/shared/ad/AdBanner';
import TextAd from 'ui/shared/ad/TextAd';
import Page from 'ui/shared/Page/Page';
......@@ -40,7 +41,8 @@ const TokenPageContent = () => {
useEffect(() => {
if (tokenQuery.data) {
const tokenName = `${ tokenQuery.data.name } (${ tokenQuery.data.symbol })`;
const tokenSymbol = tokenQuery.data.symbol ? ` (${ tokenQuery.data.symbol })` : '';
const tokenName = `${ tokenQuery.data.name || 'Unnamed' }${ tokenSymbol }`;
const title = document.getElementsByTagName('title')[0];
if (title) {
title.textContent = title.textContent?.replace(tokenQuery.data.address, tokenName) || title.textContent;
......@@ -88,6 +90,8 @@ const TokenPageContent = () => {
pagination = holdersQuery.pagination;
}
const tokenSymbolText = tokenQuery.data?.symbol ? ` (${ trimTokenSymbol(tokenQuery.data.symbol) })` : '';
return (
<Page>
{ tokenQuery.isLoading ? (
......@@ -99,7 +103,7 @@ const TokenPageContent = () => {
<>
<TextAd mb={ 6 }/>
<PageTitle
text={ `${ tokenQuery.data?.name } (${ tokenQuery.data?.symbol }) token` }
text={ `${ tokenQuery.data?.name || 'Unnamed' }${ tokenSymbolText } token` }
backLinkUrl={ hasGoBackLink ? appProps.referrer : undefined }
backLinkLabel="Back to tokens list"
additionalsLeft={ (
......
......@@ -48,7 +48,7 @@ const TransactionPageContent = () => {
});
const additionals = (
<Flex justifyContent="space-between" alignItems="center" flexGrow={ 1 }>
<Flex justifyContent="space-between" alignItems="center" flexGrow={ 1 } flexWrap="wrap">
{ data?.tx_tag && <Tag my={ 2 }>{ data.tx_tag }</Tag> }
{ explorersLinks.length > 0 && (
<Flex
......
......@@ -7,6 +7,7 @@ import blockIcon from 'icons/block.svg';
import txIcon from 'icons/transactions.svg';
import highlightText from 'lib/highlightText';
import link from 'lib/link/link';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
......@@ -25,7 +26,7 @@ const SearchResultListItem = ({ data, searchTerm }: Props) => {
const firstRow = (() => {
switch (data.type) {
case 'token': {
const name = data.name + (data.symbol ? ` (${ data.symbol })` : '');
const name = data.name + (data.symbol ? ` (${ trimTokenSymbol(data.symbol) })` : '');
return (
<Flex alignItems="flex-start">
......
......@@ -7,6 +7,7 @@ import blockIcon from 'icons/block.svg';
import txIcon from 'icons/transactions.svg';
import highlightText from 'lib/highlightText';
import link from 'lib/link/link';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
......@@ -24,7 +25,7 @@ const SearchResultTableItem = ({ data, searchTerm }: Props) => {
const content = (() => {
switch (data.type) {
case 'token': {
const name = data.name + (data.symbol ? ` (${ data.symbol })` : '');
const name = data.name + (data.symbol ? ` (${ trimTokenSymbol(data.symbol) })` : '');
return (
<>
<Td fontSize="sm">
......
......@@ -8,13 +8,14 @@ interface Props extends Omit<HTMLChakraProps<'div'>, 'title'> {
title: React.ReactNode;
hint: string;
children: React.ReactNode;
note?: string;
}
const DetailsInfoItem = ({ title, hint, children, ...styles }: Props) => {
const DetailsInfoItem = ({ title, hint, note, children, id, ...styles }: Props) => {
return (
<>
<GridItem py={{ base: 1, lg: 2 }} lineHeight={ 5 } { ...styles } whiteSpace="nowrap" _notFirst={{ mt: { base: 3, lg: 0 } }}>
<Flex columnGap={ 2 } alignItems="center">
<GridItem py={{ base: 1, lg: 2 }} id={ id } lineHeight={ 5 } { ...styles } whiteSpace="nowrap" _notFirst={{ mt: { base: 3, lg: 0 } }}>
<Flex columnGap={ 2 } alignItems="flex-start">
<Tooltip
label={ hint }
placement="top"
......@@ -24,7 +25,10 @@ const DetailsInfoItem = ({ title, hint, children, ...styles }: Props) => {
<Icon as={ infoIcon } boxSize={ 5 }/>
</Box>
</Tooltip>
<Text fontWeight={{ base: 700, lg: 500 }}>{ title }</Text>
<Text fontWeight={{ base: 700, lg: 500 }}>
{ title }
{ note && <Text fontWeight={ 500 } variant="secondary" fontSize="xs" className="note" align="right">{ note }</Text> }
</Text>
</Flex>
</GridItem>
<GridItem
......
......@@ -10,13 +10,14 @@ interface Props {
name?: string | null;
className?: string;
logoSize?: number;
isDisabled?: boolean;
}
const TokenSnippet = ({ symbol, hash, name, className, logoSize = 6 }: Props) => {
const TokenSnippet = ({ symbol, hash, name, className, logoSize = 6, isDisabled }: Props) => {
return (
<Flex className={ className } alignItems="center" columnGap={ 2 } w="100%">
<TokenLogo boxSize={ logoSize } hash={ hash } name={ name }/>
<AddressLink hash={ hash } alias={ name } type="token"/>
<AddressLink hash={ hash } alias={ name } type="token" isDisabled={ isDisabled }/>
{ symbol && <Text variant="secondary">({ symbol })</Text> }
</Flex>
);
......
......@@ -14,6 +14,7 @@ const AddressContractIcon = ({ className }: Props) => {
<Box
className={ className }
width="24px"
minWidth="24px"
height="24px"
borderRadius="12px"
backgroundColor={ bgColor }
......
......@@ -7,11 +7,11 @@ interface Props extends Omit<React.SVGProps<SVGGElement>, 'scale'> {
scale: d3.ScaleTime<number, number> | d3.ScaleLinear<number, number>;
disableAnimation?: boolean;
ticks: number;
tickFormat?: (domainValue: d3.AxisDomain, index: number) => string;
tickFormatGenerator?: (axis: d3.Axis<d3.NumberValue>) => (domainValue: d3.AxisDomain, index: number) => string;
anchorEl?: SVGRectElement | null;
}
const ChartAxis = ({ type, scale, ticks, tickFormat, disableAnimation, anchorEl, ...props }: Props) => {
const ChartAxis = ({ type, scale, ticks, tickFormatGenerator, disableAnimation, anchorEl, ...props }: Props) => {
const ref = React.useRef<SVGGElement>(null);
const textColorToken = useColorModeValue('blackAlpha.600', 'whiteAlpha.500');
......@@ -23,10 +23,14 @@ const ChartAxis = ({ type, scale, ticks, tickFormat, disableAnimation, anchorEl,
}
const axisGenerator = type === 'left' ? d3.axisLeft : d3.axisBottom;
const axis = tickFormat ?
axisGenerator(scale).ticks(ticks).tickFormat(tickFormat) :
axisGenerator(scale).ticks(ticks);
const axis = axisGenerator(scale).ticks(ticks);
if (tickFormatGenerator) {
axis.tickFormat(tickFormatGenerator(axis));
}
const axisGroup = d3.select(ref.current);
if (disableAnimation) {
axisGroup.call(axis);
} else {
......@@ -38,7 +42,7 @@ const ChartAxis = ({ type, scale, ticks, tickFormat, disableAnimation, anchorEl,
.attr('opacity', 1)
.attr('color', textColor)
.attr('font-size', '0.75rem');
}, [ scale, ticks, tickFormat, disableAnimation, type, textColor ]);
}, [ scale, ticks, tickFormatGenerator, disableAnimation, type, textColor ]);
React.useEffect(() => {
if (!anchorEl) {
......
......@@ -55,7 +55,7 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
}, [ isGroupedValues, rangedItems ]);
const chartData = [ { items: displayedData, name: 'Value', color } ];
const { yTickFormat, xScale, yScale } = useTimeChartController({
const { xTickFormat, yTickFormat, xScale, yScale } = useTimeChartController({
data: [ { items: displayedData, name: title, color } ],
width: innerWidth,
height: innerHeight,
......@@ -105,7 +105,7 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
type="left"
scale={ yScale }
ticks={ isEnlarged ? 6 : 3 }
tickFormat={ yTickFormat }
tickFormatGenerator={ yTickFormat }
disableAnimation
/>
......@@ -115,6 +115,7 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
transform={ `translate(0, ${ innerHeight })` }
ticks={ isMobile ? 1 : 4 }
anchorEl={ overlayRef.current }
tickFormatGenerator={ xTickFormat }
disableAnimation
/>
......
......@@ -3,6 +3,7 @@ import { useMemo } from 'react';
import type { TimeChartData } from 'ui/shared/chart/types';
import { WEEK, MONTH, YEAR } from 'lib/consts';
import formatNumberToMetricPrefix from 'lib/formatNumberToMetricPrefix';
interface Props {
......@@ -29,7 +30,8 @@ export default function useTimeChartController({ data, width, height }: Props) {
);
const yMin = useMemo(
() => d3.min(data, ({ items }) => d3.min(items, ({ value }) => value)) || 0,
// use -1 instead of 0 to correctly display the curve between two zero points.
() => d3.min(data, ({ items }) => d3.min(items, ({ value }) => value)) || -1,
[ data ],
);
......@@ -51,8 +53,27 @@ export default function useTimeChartController({ data, width, height }: Props) {
[ height, yMin, yMax ],
);
const xTickFormat = (d: d3.AxisDomain) => d.toLocaleString();
const yTickFormat = (d: d3.AxisDomain) => formatNumberToMetricPrefix(Number(d));
const xTickFormat = (axis: d3.Axis<d3.NumberValue>) => (d: d3.AxisDomain) => {
let format: (date: Date) => string;
const scale = axis.scale();
const extent = scale.domain();
const span = Number(extent[1]) - Number(extent[0]);
if (span > YEAR) {
format = d3.timeFormat('%Y');
} else if (span > 2 * MONTH) {
format = d3.timeFormat('%b');
} else if (span > WEEK) {
format = d3.timeFormat('%b %d');
} else {
format = d3.timeFormat('%a %d');
}
return format(d as Date);
};
const yTickFormat = () => (d: d3.AxisDomain) => formatNumberToMetricPrefix(Number(d));
return {
xTickFormat,
......
import { chakra, Tooltip, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import type { SmartContract } from 'types/api/contract';
import type { ResourceError } from 'lib/api/resources';
import useApiQuery from 'lib/api/useApiQuery';
import ContentLoader from 'ui/shared/ContentLoader';
interface Props {
addressHash: string;
}
function composeSources(contract: SmartContract | undefined) {
if (!contract) {
return {};
}
const additionalSources = contract.additional_sources?.reduce<Record<string, string>>((result, item) => {
result[item.file_path] = item.source_code;
return result;
}, {});
return {
[contract.file_path || 'index.sol']: contract.source_code,
...additionalSources,
};
}
const Sol2UmlDiagram = ({ addressHash }: Props) => {
const contractQuery = useApiQuery<'contract', ResourceError>('contract', {
pathParams: { id: addressHash },
queryOptions: {
enabled: Boolean(addressHash),
refetchOnMount: false,
},
});
const umlQuery = useApiQuery('visualize_sol2uml', {
fetchParams: {
method: 'POST',
body: {
sources: composeSources(contractQuery.data),
},
},
queryOptions: {
enabled: Boolean(contractQuery.data),
refetchOnMount: false,
},
});
const imgUrl = `data:image/svg+xml;base64,${ umlQuery.data?.svg }`;
const imgFilter = useColorModeValue('invert(0)', 'invert(1)');
const handleClick = React.useCallback(() => {
const image = new Image();
image.src = imgUrl;
const newWindow = window.open(imgUrl);
newWindow?.document.write(image.outerHTML);
}, [ imgUrl ]);
if (!addressHash) {
throw Error('Contract address is not provided', { cause: { status: 404 } as unknown as Error });
}
if (contractQuery.isError) {
throw Error('Contract fetch error', { cause: contractQuery.error as unknown as Error });
}
if (umlQuery.isError) {
throw Error('Uml diagram fetch error', { cause: contractQuery.error as unknown as Error });
}
if (contractQuery.isLoading || umlQuery.isLoading) {
return <ContentLoader/>;
}
if (!umlQuery.data.svg) {
return <span>No data for visualization</span>;
}
return (
<Tooltip label="Click on image to zoom" placement="top">
<chakra.img
src={ `data:image/svg+xml;base64,${ umlQuery.data.svg }` }
alt={ `Contract ${ contractQuery.data.name } UML diagram` }
onClick={ handleClick }
cursor="pointer"
filter={ imgFilter }
/>
</Tooltip>
);
};
export default React.memo(Sol2UmlDiagram);
......@@ -103,15 +103,15 @@ const TokenDetails = ({ tokenQuery }: Props) => {
{ `$${ totalValue?.usd }` }
</DetailsInfoItem>
) }
{ totalValue?.valueStr && (
<DetailsInfoItem
title="Max total supply"
hint="The total amount of tokens issued."
alignSelf="center"
>
{ `${ totalValue.valueStr } ${ symbol }` }
</DetailsInfoItem>
) }
<DetailsInfoItem
title="Max total supply"
hint="The total amount of tokens issued."
alignSelf="center"
wordBreak="break-word"
whiteSpace="pre-wrap"
>
{ `${ totalValue?.valueStr || 0 } ${ symbol || '' }` }
</DetailsInfoItem>
<DetailsInfoItem
title="Holders"
hint="Number of accounts holding the token."
......
......@@ -57,7 +57,7 @@ const TokenHoldersContent = ({ holdersQuery, tokenQuery }: Props) => {
return (
<>
{ bar }
{ !isMobile && <TokenHoldersTable data={ items } token={ tokenQuery.data }/> }
{ !isMobile && <TokenHoldersTable data={ items } token={ tokenQuery.data } top={ holdersQuery.isPaginationVisible ? 80 : 0 }/> }
{ isMobile && <TokenHoldersList data={ items } token={ tokenQuery.data }/> }
</>
);
......
......@@ -12,7 +12,7 @@ test('base view', async({ mount }) => {
const component = await mount(
<TestApp>
<Box h="128px"/>
<TokenHoldersTable data={ tokenHolders.items } token={ tokenInfo }/>
<TokenHoldersTable data={ tokenHolders.items } token={ tokenInfo } top={ 80 }/>
</TestApp>,
);
......
......@@ -9,12 +9,13 @@ import TokenHoldersTableItem from 'ui/token/TokenHolders/TokenHoldersTableItem';
interface Props {
data: Array<TokenHolder>;
token: TokenInfo;
top: number;
}
const TokenHoldersTable = ({ data, token }: Props) => {
const TokenHoldersTable = ({ data, token, top }: Props) => {
return (
<Table variant="simple" size="sm">
<Thead top={ 80 }>
<Thead top={ top }>
<Tr>
<Th>Holder</Th>
<Th isNumeric width="300px">Quantity</Th>
......
......@@ -63,7 +63,7 @@ test('erc1155 +@mobile', async({ mount }) => {
<TestApp>
<Box h={{ base: '134px', lg: '100px' }}/>
<TokenTransfer
token={{ ...tokenInfo, type: 'ERC-1155' }}
token={{ ...tokenInfo, type: 'ERC-1155', symbol: tokenTransferMock.erc1155multiple.token.symbol }}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore:
transfersQuery={{
......
......@@ -91,7 +91,7 @@ const TokenTransfer = ({ transfersQuery, token }: Props) => {
<Hide below="lg" ssr={ false }>
<TokenTransferTable
data={ items }
top={ 80 }
top={ isPaginationVisible ? 80 : 0 }
// token transfers query depends on token data
// so if we are here, we definitely have token data
token={ token as TokenInfo }
......
......@@ -7,6 +7,7 @@ import type { TokenTransfer } from 'types/api/tokenTransfer';
import eastArrowIcon from 'icons/arrows/east.svg';
import transactionIcon from 'icons/transactions.svg';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
......@@ -69,14 +70,15 @@ const TokenTransferListItem = ({
<AddressLink ml={ 2 } fontWeight="500" hash={ to.hash } type="address_token" tokenHash={ token.address }/>
</Address>
</Flex>
{ value && (
{ value && (token.type === 'ERC-20' || token.type === 'ERC-1155') && (
<Flex columnGap={ 2 } w="100%">
<Text fontWeight={ 500 } flexShrink={ 0 }>Value</Text>
<Text variant="secondary">{ value }</Text>
<Text>{ token.symbol }</Text>
<Text>{ trimTokenSymbol(token.symbol) }</Text>
</Flex>
) }
{ 'token_id' in total && <TokenTransferNft hash={ token.address } id={ total.token_id }/> }
{ 'token_id' in total && (token.type === 'ERC-721' || token.type === 'ERC-1155') &&
<TokenTransferNft hash={ token.address } id={ total.token_id }/> }
</ListItemMobile>
);
};
......
......@@ -4,6 +4,7 @@ import React from 'react';
import type { TokenInfo } from 'types/api/tokenInfo';
import type { TokenTransfer } from 'types/api/tokenTransfer';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import { default as Thead } from 'ui/shared/TheadSticky';
import TokenTransferTableItem from 'ui/token/TokenTransfer/TokenTransferTableItem';
......@@ -22,13 +23,13 @@ const TokenTransferTable = ({ data, top, token, showSocketInfo, socketInfoAlert,
<Table variant="simple" size="sm">
<Thead top={ top }>
<Tr>
<Th width="40%">Txn hash</Th>
<Th width={ token.type === 'ERC-1155' ? '60%' : '80%' }>Txn hash</Th>
<Th width="164px">Method</Th>
<Th width="148px">From</Th>
<Th width="36px" px={ 0 }/>
<Th width="218px" >To</Th>
{ (token.type === 'ERC-721' || token.type === 'ERC-1155') && <Th width="20%" isNumeric={ token.type === 'ERC-721' }>Token ID</Th> }
{ (token.type === 'ERC-20' || token.type === 'ERC-1155') && <Th width="20%" isNumeric>Value { token.symbol }</Th> }
{ (token.type === 'ERC-20' || token.type === 'ERC-1155') && <Th width="20%" isNumeric>Value { trimTokenSymbol(token.symbol) }</Th> }
</Tr>
</Thead>
<Tbody>
......
import { Tr, Td, Tag, Text, Icon } from '@chakra-ui/react';
import { Tr, Td, Tag, Text, Icon, Grid } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
......@@ -35,10 +35,12 @@ const TokenTransferTableItem = ({
return (
<Tr alignItems="top">
<Td>
<Address display="inline-flex" maxW="100%" fontWeight={ 600 } lineHeight="30px">
<AddressLink type="transaction" hash={ txHash }/>
</Address>
{ timestamp && <Text color="gray.500" fontWeight="400" mt="10px">{ timeAgo }</Text> }
<Grid alignItems="center" gridTemplateColumns="auto 130px" width="fit-content">
<Address display="inline-flex" fontWeight={ 600 } lineHeight="30px">
<AddressLink type="transaction" hash={ txHash }/>
</Address>
{ timestamp && <Text color="gray.500" fontWeight="400" ml="10px">{ timeAgo }</Text> }
</Grid>
</Td>
<Td>
{ method ? <Tag colorScheme="gray">{ method }</Tag> : '-' }
......
......@@ -4,6 +4,7 @@ import React from 'react';
import nftIcon from 'icons/nft_shield.svg';
import link from 'lib/link/link';
import AddressLink from 'ui/shared/address/AddressLink';
import HashStringShorten from 'ui/shared/HashStringShorten';
import TokenSnippet from 'ui/shared/TokenSnippet/TokenSnippet';
interface Props {
......@@ -23,7 +24,9 @@ const NftTokenTransferSnippet = ({ value, name, hash, symbol, tokenId }: Props)
<Text fontWeight={ 500 } as="span">For { num } token ID:</Text>
<Box display="inline-flex" alignItems="center">
<Icon as={ nftIcon } boxSize={ 6 } mr={ 1 }/>
<Link href={ url } fontWeight={ 600 }>{ tokenId }</Link>
<Link href={ url } fontWeight={ 600 } overflow="hidden">
{ tokenId.length > 8 ? <HashStringShorten hash={ tokenId }/> : tokenId }
</Link>
</Box>
{ name ? (
<TokenSnippet symbol={ symbol } hash={ hash } name={ name } w="auto" logoSize={ 5 } columnGap={ 1 }/>
......
......@@ -124,3 +124,20 @@ test('pending', async({ mount, page }) => {
await expect(component).toHaveScreenshot();
});
test('with actions uniswap +@mobile +@dark-mode', async({ mount, page }) => {
await page.route(API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(txMock.withActionsUniswap),
}));
const component = await mount(
<TestApp>
<TxDetails/>
</TestApp>,
{ hooksConfig },
);
await insertAdPlaceholder(page);
await expect(component).toHaveScreenshot();
});
......@@ -39,6 +39,7 @@ import RawInputData from 'ui/shared/RawInputData';
import TextSeparator from 'ui/shared/TextSeparator';
import TxStatus from 'ui/shared/TxStatus';
import Utilization from 'ui/shared/Utilization/Utilization';
import TxDetailsActions from 'ui/tx/details/TxDetailsActions';
import TxDetailsSkeleton from 'ui/tx/details/TxDetailsSkeleton';
import TxDetailsTokenTransfers from 'ui/tx/details/TxDetailsTokenTransfers';
import TxRevertReason from 'ui/tx/details/TxRevertReason';
......@@ -89,6 +90,8 @@ const TxDetails = () => {
...toAddress.watchlist_names || [],
].map((tag) => <Tag key={ tag.label }>{ tag.display_name }</Tag>);
const actionsExist = data.actions && data.actions.length > 0;
const executionSuccessBadge = toAddress.is_contract && data.result === 'success' ? (
<Tooltip label="Contract execution completed">
<chakra.span display="inline-flex" ml={ 2 } mr={ 1 }>
......@@ -104,6 +107,16 @@ const TxDetails = () => {
</Tooltip>
) : null;
const divider = (
<GridItem
colSpan={{ base: undefined, lg: 2 }}
mt={{ base: 2, lg: 3 }}
mb={{ base: 0, lg: 3 }}
borderBottom="1px solid"
borderColor="divider"
/>
);
return (
<Grid columnGap={ 8 } rowGap={{ base: 3, lg: 3 }} templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }}>
{ socketStatus && (
......@@ -180,13 +193,16 @@ const TxDetails = () => {
<AdBanner/>
</DetailsInfoItem>
) }
<GridItem
colSpan={{ base: undefined, lg: 2 }}
mt={{ base: 2, lg: 3 }}
mb={{ base: 0, lg: 3 }}
borderBottom="1px solid"
borderColor="divider"
/>
{ divider }
{ actionsExist && (
<>
<TxDetailsActions actions={ data.actions }/>
{ divider }
</>
) }
<DetailsInfoItem
title="From"
hint="Address (external or contract) sending the transaction."
......@@ -237,13 +253,8 @@ const TxDetails = () => {
</DetailsInfoItem>
{ data.token_transfers && <TxDetailsTokenTransfers data={ data.token_transfers } txHash={ data.hash }/> }
<GridItem
colSpan={{ base: undefined, lg: 2 }}
mt={{ base: 2, lg: 3 }}
mb={{ base: 0, lg: 3 }}
borderBottom="1px solid"
borderColor="divider"
/>
{ divider }
<DetailsInfoItem
title="Value"
hint="Value sent in the native token (and USD) if applicable."
......
......@@ -84,7 +84,7 @@ const TxTokenTransfer = () => {
return (
<>
<Hide below="lg" ssr={ false }>
<TokenTransferTable data={ items } top={ 80 }/>
<TokenTransferTable data={ items } top={ isActionBarHidden ? 0 : 80 }/>
</Hide>
<Show below="lg" ssr={ false }>
<TokenTransferList data={ items }/>
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
File mode changed from 100644 to 100755
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment