Commit a88ab13a authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into uml

parents f3315f4b 8dabafd1
......@@ -14,9 +14,58 @@ env:
BASTION_SSH_KEY: ${{secrets.BASTION_SSH_KEY}}
jobs:
push_to_registry:
name: Push Docker image to registry
runs-on: ubuntu-latest
outputs:
shortSha: ${{ steps.output-step.outputs.short-sha }}
steps:
- name: Check out the repo
uses: actions/checkout@v3
- name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
# Will automatically make nice tags, see the table here https://github.com/docker/metadata-action#basic
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: ghcr.io/blockscout/frontend
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Add SHORT_SHA env property with commit short sha
run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
- name: Add outputs
run: |
echo "::set-output name=short-sha::${{ env.GITHUB_EVENT_PULL_REQUEST_HEAD_SHA_SHORT }}"
id: output-step
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile
push: true
cache-from: type=gha
tags: ghcr.io/blockscout/frontend:prerelease-${{ env.GITHUB_REF_NAME_SLUG }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
GIT_COMMIT_SHA=${{ env.GITHUB_EVENT_PULL_REQUEST_HEAD_SHA_SHORT }}
deploy_and_tests:
if: github.event.review.state == 'approved'
# needs: push_to_registry
needs: push_to_registry
uses: blockscout/blockscout-ci-cd/.github/workflows/e2e_new.yaml@master
with:
appNamespace: e2e-front-$GITHUB_SHA_SHORT
......
......@@ -49,3 +49,5 @@ yarn-error.log*
/playwright/.cache/
/playwright/.browser/
/playwright/envs.js
**.dec**
......@@ -65,6 +65,31 @@ const oldUrls = [
oldPath: '/address/:id/tokens/:hash/token-transfers',
newPath: `${ PATHS.address_index }?tab=token_transfers&token=:hash`,
},
// contract verification
{
oldPath: '/address/:id/contract_verifications/new',
newPath: `${ PATHS.address_contract_verification }`,
},
{
oldPath: '/address/:id/verify-via-flattened-code/new',
newPath: `${ PATHS.address_contract_verification }?method=flatten_source_code`,
},
{
oldPath: '/address/:id/verify-via-standard-json-input/new',
newPath: `${ PATHS.address_contract_verification }?method=standard_input`,
},
{
oldPath: '/address/:id/verify-via-metadata-json/new',
newPath: `${ PATHS.address_contract_verification }?method=sourcify`,
},
{
oldPath: '/address/:id/verify-via-multi-part-files/new',
newPath: `${ PATHS.address_contract_verification }?method=multi_part_file`,
},
{
oldPath: '/address/:id/verify-vyper-contract/new',
newPath: `${ PATHS.address_contract_verification }?method=vyper_contract`,
},
];
async function redirects() {
......
......@@ -22,8 +22,6 @@ blockscout:
_default: ENC[AES256_GCM,data:/+0GBBYW34fDHIAPpHCbNucu2FUH9VAI+wBV6dLHxvYk0whtXikiClentSk=,iv:ekOPgF0H/0/MG++cLi0gH+AzqWafkqFL9R7B8G8jPo4=,tag:QcI69GzicGVV96+EDOR81w==,type:str]
SECRET_KEY_BASE:
_default: ENC[AES256_GCM,data:Cqga1O4fbdtx5GfEQjJEPYWqeig7SBAdKiIif9sGCNrdy5FSHr43uskfdOdiS3uhaHm925jhPf+/nvs7VRzaqSAJCB2HBrVjJLOTQItVEw5rGus9Ma2plubDdrXh2CHO14mjE6f1/QOq2MLC1S79MqeHxpgqS0sgMflKopWa3/4=,iv:z1mBiInLa3kutmPFuX+R96rUSGcjFr/UH6cn/UbM0tg=,tag:8TRBcnTENdguj2BOs4Qrmw==,type:str]
SECRET_KEY_GUARDIAN:
_default: ENC[AES256_GCM,data:vpsqZh2sBKUiG4LtTVZyi3qBUU2EMPIrAp9k4X4iSYNvJM7iDGjq/du3EmsQCccvlr54x3opyypHYsdA7gL2Cg==,iv:9q8ePtwr2CDieE2ZU3UyxJ1wtKzIOY1oJ7rZmXCcWWM=,tag:BFO+PztjAOfQHh9mpC/ZJA==,type:str]
DATABASE_URL:
_default: ENC[AES256_GCM,data:r5JQJH+pq4zwo9ceFP4I8inttRFFxKKaw9+l9hDqeDMcEIPljAfJxvSF3noVlyfNtqO0ru+3GaTumDamdb9Hw2Y=,iv:OwCvrIgXpxVMI5QQfT7ZDGcsXBdzIcuv0wUSxtwkYrM=,tag:o0GFp3F+jTxw8Hg4Ce/IpQ==,type:str]
ACCOUNT_DATABASE_URL:
......@@ -45,9 +43,9 @@ blockscout:
scVerifier:
environment:
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__ACCESS_KEY:
_default: ENC[AES256_GCM,data:u9zFU9th8ShQ5HIaehYgMzOorx7Tfg7K,iv:ytd6IoU8PIMqbZ4W7kgkL5S4KV4MbvHfxMH2IthQnYE=,tag:0kgXuYSBZZp0LHG3m5EfYg==,type:str]
_default: ENC[AES256_GCM,data:OoT0i3qKMhNWWsMSWd2VWhWcISIH98Xa,iv:FNGRrn/7juKquC1J9DQJYSl6KF+FNPrfojYGLNgzrMk=,tag:phx1JSOQ/9FwWZ3KxQAmoQ==,type:str]
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__SECRET_KEY:
_default: ENC[AES256_GCM,data:t78wGcNs4gqMav1TJaIyBUE4vxsaXloMSmPobix2zvpL2MfNl/6ERA==,iv:IdQot/B7QH9mJWFypTn9OIS+WOeq+B5KZYuYMkQ8+G4=,tag:xXX0WIIM5enPGaibiywtBg==,type:str]
_default: ENC[AES256_GCM,data:YgnGKw/S7lP5abYpG4fke8fXWjd5OHlxnEEyQsEHTv6/ndwSUg9VVA==,iv:Whimy+pKiSzmhaHhIWxhnV0Ok30HEzH4PrqwFL/W8AQ=,tag:FHxNEaobhBu8SziuxE+awg==,type:str]
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__BUCKET:
_default: ENC[AES256_GCM,data:495QOb9qNhVugnOtaw==,iv:4h9S63+0P5qzeObiu5LJ3YKnFboSJ6WDE8tPnavySxg=,tag:ld3u6nYJH4qv3eN/pon5ow==,type:str]
postgres:
......@@ -134,8 +132,8 @@ sops:
azure_kv: []
hc_vault: []
age: []
lastmodified: "2023-01-18T10:42:25Z"
mac: ENC[AES256_GCM,data:QZixaOd5zjucSuwtyBcgACtNynt2X23B6Dxqxm2ZQtsvwaqz51i7TOe1BlmORT+71KDJhnaDmodc+xcNAta0K0e8uS0qFvDaE3aew77yfpn02kM5/2PwYc2xlh7nKg6dsfddxERx5UzaWLQWnU7ODN7hpsZ3Q5Hurf9fI5APleI=,iv:XufptrfeRp63XuwLHEmUFUEi5kwsYtNNaJ63fyaLqOQ=,tag:oFOiQSlhPiw3I9Pe0lPaGw==,type:str]
lastmodified: "2023-01-30T18:25:02Z"
mac: ENC[AES256_GCM,data:osscQyqUAUatyEzeIHCj10+uj1vCzKJC9W0IyChPKR+KQSEW0I2l20nFcSMCb9sGWVNXzQCfltsDTXzHM5nIuzP2qn/qVmwntQcKB4ibGzaE1gshVQA6deQ28UvGkMjkNTKszqtONgc02G1Kl0d7yiigx+jAQDJNT94amwO/OPs=,iv:6g4T3KJTH0u96y+QBjjJPwjuU4+5psd1cIw+AddvWdE=,tag:Z59JO8iZwfbx80wACXk6sA==,type:str]
pgp:
- created_at: "2022-09-14T13:42:28Z"
enc: |
......
......@@ -166,7 +166,7 @@ postgres:
# strategy: Recreate
persistence: true
storage: 1000Gi
storage: 1200Gi
resources:
limits:
......
......@@ -34,8 +34,6 @@ blockscout:
_default: ENC[AES256_GCM,data:/+0GBBYW34fDHIAPpHCbNucu2FUH9VAI+wBV6dLHxvYk0whtXikiClentSk=,iv:ekOPgF0H/0/MG++cLi0gH+AzqWafkqFL9R7B8G8jPo4=,tag:QcI69GzicGVV96+EDOR81w==,type:str]
SECRET_KEY_BASE:
_default: ENC[AES256_GCM,data:Cqga1O4fbdtx5GfEQjJEPYWqeig7SBAdKiIif9sGCNrdy5FSHr43uskfdOdiS3uhaHm925jhPf+/nvs7VRzaqSAJCB2HBrVjJLOTQItVEw5rGus9Ma2plubDdrXh2CHO14mjE6f1/QOq2MLC1S79MqeHxpgqS0sgMflKopWa3/4=,iv:z1mBiInLa3kutmPFuX+R96rUSGcjFr/UH6cn/UbM0tg=,tag:8TRBcnTENdguj2BOs4Qrmw==,type:str]
SECRET_KEY_GUARDIAN:
_default: ENC[AES256_GCM,data:vpsqZh2sBKUiG4LtTVZyi3qBUU2EMPIrAp9k4X4iSYNvJM7iDGjq/du3EmsQCccvlr54x3opyypHYsdA7gL2Cg==,iv:9q8ePtwr2CDieE2ZU3UyxJ1wtKzIOY1oJ7rZmXCcWWM=,tag:BFO+PztjAOfQHh9mpC/ZJA==,type:str]
DATABASE_URL:
_default: ENC[AES256_GCM,data:r5JQJH+pq4zwo9ceFP4I8inttRFFxKKaw9+l9hDqeDMcEIPljAfJxvSF3noVlyfNtqO0ru+3GaTumDamdb9Hw2Y=,iv:OwCvrIgXpxVMI5QQfT7ZDGcsXBdzIcuv0wUSxtwkYrM=,tag:o0GFp3F+jTxw8Hg4Ce/IpQ==,type:str]
ACCOUNT_DATABASE_URL:
......@@ -45,9 +43,9 @@ blockscout:
scVerifier:
environment:
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__ACCESS_KEY:
_default: ENC[AES256_GCM,data:u9zFU9th8ShQ5HIaehYgMzOorx7Tfg7K,iv:ytd6IoU8PIMqbZ4W7kgkL5S4KV4MbvHfxMH2IthQnYE=,tag:0kgXuYSBZZp0LHG3m5EfYg==,type:str]
_default: ENC[AES256_GCM,data:uRdjwPkkOLAc/RfDO0mdwhlIBCT+PUwy,iv:asZ+vHrz1FRXKADPmbqxObMGADKZOj9FyPwDEbO1LAc=,tag://hA2VevmZzYkfQASCwnVQ==,type:str]
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__SECRET_KEY:
_default: ENC[AES256_GCM,data:t78wGcNs4gqMav1TJaIyBUE4vxsaXloMSmPobix2zvpL2MfNl/6ERA==,iv:IdQot/B7QH9mJWFypTn9OIS+WOeq+B5KZYuYMkQ8+G4=,tag:xXX0WIIM5enPGaibiywtBg==,type:str]
_default: ENC[AES256_GCM,data:+QQlk2NxxK5w9whMkE2Vy4NNwbjkWVNcvAD6YlES/6oq7ZuUgPKf8A==,iv:n/Q9Brte/iuvOputNfdqrIzvBWcoVok0/CHPlgGhXds=,tag:mltSdFblt/T+SV2SrBEe3w==,type:str]
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__BUCKET:
_default: ENC[AES256_GCM,data:495QOb9qNhVugnOtaw==,iv:4h9S63+0P5qzeObiu5LJ3YKnFboSJ6WDE8tPnavySxg=,tag:ld3u6nYJH4qv3eN/pon5ow==,type:str]
postgres:
......@@ -80,8 +78,8 @@ sops:
azure_kv: []
hc_vault: []
age: []
lastmodified: "2023-01-18T20:08:13Z"
mac: ENC[AES256_GCM,data:Qk1EyzFy/tYS5bDREC+EROmZ2mhSLNAa6ZJNByBYqMOqYTcrQhYC95I7/2qAHNnjTqNWwPSKb7LxEDSk60I3FZkXZr42gribWSoblf95KRbKsSiBhX1omE6tAK6gEpWuLuhwdLrUOdWM4fCUnwdFn7IV4y8KknhbSZ/yE0kNEnY=,iv:yc4E7yGZ8ftVU3XN2VhwhvjwtSbpXLWFgAG897TbrLk=,tag:SZrlReFY1cN9u54hsgJBKQ==,type:str]
lastmodified: "2023-01-30T18:24:47Z"
mac: ENC[AES256_GCM,data:KYXWcGq3Irq7styUQke+YFlpClE5LzmPLb7tIJcWmTTnksQKZcLibPJIoEBbPLjznJeKpY/AU777CXStihtZPcWeey7NO8VWk2LcVI1+r+/jzj1Gu0bqfIXIqTXFRy46QBjNsbRYMlZRyWlXSKlXXx4Ahp86sHu5nOHJ3oMe1FY=,iv:70iEvTB2J8lZuQzatWg4OKBN2/vYVoNabdJtbp7X8Y8=,tag:SYI8NKMOq67tYPmSWVQiMA==,type:str]
pgp:
- created_at: "2022-09-14T13:42:28Z"
enc: |
......
<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 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"/>
</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 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"/>
</svg>
......@@ -10,3 +10,5 @@ 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;
......@@ -14,7 +14,7 @@
"token_index": "/token/:hash",
"token_instance_item": "/token/:hash/instance/:id",
"address_index": "/address/:id",
"address_contract_verification": "/address/:id/contract_verifications/new",
"address_contract_verification": "/address/:id/contract_verification",
"accounts": "/accounts",
"apps": "/apps",
"app_index": "/apps/:id",
......
// 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,
];
......@@ -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 },
],
};
......
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import type { PageParams } from 'lib/next/address/types';
import getSeo from 'lib/next/address/getSeo';
import ContractVerification from 'ui/pages/ContractVerification';
const ContractVerificationPage: NextPage<PageParams> = ({ id }: PageParams) => {
const { title, description } = getSeo({ id });
return (
<>
<Head>
<title>{ title }</title>
<meta name="description" content={ description }/>
</Head>
<ContractVerification/>
</>
);
};
export default ContractVerificationPage;
export { getServerSideProps } from 'lib/next/getServerSideProps';
{
"foo": "bar"
}
\ No newline at end of file
{
"foo": "bar",
"baz": ["baz","baz","baz"]
}
\ No newline at end of file
{
"id": 1,
"title": "iPhone 9",
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nec leo odio. Vivamus iaculis faucibus tempus. Duis dapibus, ligula eu consequat ornare, sapien nisl laoreet arcu, eu pharetra dolor urna eget ligula. Sed facilisis pretium risus in finibus. Maecenas egestas orci euismod venenatis aliquam. Vivamus sed pretium dui, vitae porta ipsum. Donec elit tortor, imperdiet eu nulla at, condimentum consequat nisi. Phasellus mollis sem aliquam dolor lacinia bibendum. Mauris pulvinar vestibulum sodales. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nullam vitae facilisis odio. Mauris in ultricies nulla. Sed ornare consequat nulla. Cras pellentesque, erat at gravida rutrum, erat neque facilisis magna, et iaculis enim tortor ornare felis. Quisque mollis dignissim magna. In vitae ultricies felis. Quisque in ipsum non lorem ornare elementum vel vitae nisl. Integer luctus pulvinar dui a eleifend. Aliquam iaculis odio vitae metus ullamcorper, sed semper turpis consectetur. In convallis, justo a aliquet rhoncus, erat metus fringilla turpis, eget imperdiet purus est quis magna. Curabitur ornare mattis malesuada. In vitae lacinia est. Sed sit amet magna eget dolor tempus euismod.",
"price": 549,
"discountPercentage": 12.96,
"rating": 4.69,
"stock": 94,
"brand": "Apple",
"category": "smartphones",
"thumbnail": "https://i.dummyjson.com/data/products/1/thumbnail.jpg",
"images": [
"https://i.dummyjson.com/data/products/1/1.jpg",
"https://i.dummyjson.com/data/products/1/2.jpg",
"https://i.dummyjson.com/data/products/1/3.jpg",
"https://i.dummyjson.com/data/products/1/4.jpg",
"https://i.dummyjson.com/data/products/1/thumbnail.jpg"
]
}
\ No newline at end of file
......@@ -2,6 +2,7 @@ import { checkboxAnatomy as parts } from '@chakra-ui/anatomy';
import {
createMultiStyleConfigHelpers,
defineStyle,
cssVar,
} from '@chakra-ui/styled-system';
import { mode } from '@chakra-ui/theme-tools';
import { runIfFn } from '@chakra-ui/utils';
......@@ -9,6 +10,8 @@ import { runIfFn } from '@chakra-ui/utils';
const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(parts.keys);
const $size = cssVar('checkbox-size');
const baseStyleControl = defineStyle((props) => {
const { colorScheme: c } = props;
......@@ -28,6 +31,24 @@ const baseStyleControl = defineStyle((props) => {
};
});
const sizes = {
sm: definePartsStyle({
control: { [$size.variable]: 'sizes.3' },
label: { fontSize: 'sm' },
icon: { fontSize: '3xs' },
}),
md: definePartsStyle({
control: { [$size.variable]: 'sizes.4' },
label: { fontSize: 'md' },
icon: { fontSize: '2xs' },
}),
lg: definePartsStyle({
control: { [$size.variable]: 'sizes.5' },
label: { fontSize: 'md' },
icon: { fontSize: '2xs' },
}),
};
const baseStyleLabel = defineStyle({
_disabled: { opacity: 0.2 },
});
......@@ -39,6 +60,7 @@ const baseStyle = definePartsStyle((props) => ({
const Checkbox = defineMultiStyleConfig({
baseStyle,
sizes,
});
export default Checkbox;
const sizes = {
sm: {
field: {
px: '0',
height: '36px',
},
},
md: {
field: {
px: '0',
height: '56px',
},
},
lg: {
field: {
px: '0',
height: '76px',
},
},
};
const FancySelect = {
sizes,
};
export default FancySelect;
......@@ -4,6 +4,7 @@ import type { StyleFunctionProps } from '@chakra-ui/theme-tools';
import { getColor } from '@chakra-ui/theme-tools';
import getDefaultFormColors from '../utils/getDefaultFormColors';
import FancySelect from './FancySelect';
import FormLabel from './FormLabel';
import Input from './Input';
import Textarea from './Textarea';
......@@ -57,22 +58,32 @@ function getFloatingVariantStylesForSize(size: 'md' | 'lg', props: StyleFunction
label: activeLabelStyles,
'input, textarea': activeInputStyles,
},
'&[data-active=true] label': activeLabelStyles,
// label styles
label: FormLabel.sizes?.[size](props) || {},
'input:not(:placeholder-shown) + label, textarea:not(:placeholder-shown) + label': activeLabelStyles,
'input[aria-invalid=true] + label, textarea[aria-invalid=true] + label': {
[`
input[aria-invalid=true] + label,
textarea[aria-invalid=true] + label,
&[aria-invalid=true] label
`]: {
color: getColor(theme, errorColor),
},
// input styles
input: Input.sizes?.[size].field,
'input[aria-autocomplete=list]': FancySelect.sizes[size].field,
textarea: Textarea.sizes?.[size],
'input, textarea': {
padding: inputPx,
},
'input:not(:placeholder-shown), textarea:not(:placeholder-shown)': activeInputStyles,
'input[disabled] + label, textarea[disabled] + label': {
[`
input[disabled] + label,
textarea[disabled] + label,
&[aria-disabled=true] label
`]: {
backgroundColor: 'transparent',
},
......@@ -80,7 +91,11 @@ function getFloatingVariantStylesForSize(size: 'md' | 'lg', props: StyleFunction
'input:not(:placeholder-shown) + label .chakra-form__required-indicator, textarea:not(:placeholder-shown) + label .chakra-form__required-indicator': {
color: getColor(theme, focusPlaceholderColor),
},
'input[aria-invalid=true] + label .chakra-form__required-indicator, textarea[aria-invalid=true] + label .chakra-form__required-indicator': {
[`
input[aria-invalid=true] + label .chakra-form__required-indicator,
textarea[aria-invalid=true] + label .chakra-form__required-indicator,
&[aria-invalid=true] .chakra-form__required-indicator
`]: {
color: getColor(theme, errorColor),
},
},
......
......@@ -63,6 +63,9 @@ const sizes = {
_focusWithin: {
padding: '16px 24px 2px 24px',
},
'&[data-fancy=true]': {
right: '36px',
},
};
}
......@@ -78,6 +81,9 @@ const sizes = {
_focusWithin: {
padding: '10px 16px 2px 16px',
},
'&[data-fancy=true]': {
right: '36px',
},
};
}
......
......@@ -21,8 +21,24 @@ const baseStyle = definePartsStyle({
container: baseStyleContainer,
});
const sizes = {
md: definePartsStyle({
control: { w: '4', h: '4' },
label: { fontSize: 'md' },
}),
lg: definePartsStyle({
control: { w: '5', h: '5' },
label: { fontSize: 'md' },
}),
sm: definePartsStyle({
control: { width: '3', height: '3' },
label: { fontSize: 'sm' },
}),
};
const Radio = defineMultiStyleConfig({
baseStyle,
sizes,
});
export default Radio;
import { selectAnatomy as parts } from '@chakra-ui/anatomy';
import {
createMultiStyleConfigHelpers,
defineStyle,
} from '@chakra-ui/styled-system';
import { mode } from '@chakra-ui/theme-tools';
......@@ -26,11 +27,52 @@ const variantOutline = definePartsStyle((props) => {
};
});
const iconSpacing = defineStyle({
paddingInlineEnd: '8',
});
const sizes = {
lg: {
...Input.sizes?.lg,
field: {
...Input.sizes?.lg.field,
...iconSpacing,
},
},
md: {
...Input.sizes?.md,
field: {
...Input.sizes?.md.field,
...iconSpacing,
},
},
sm: {
...Input.sizes?.sm,
field: {
...Input.sizes?.sm.field,
...iconSpacing,
},
},
xs: {
...Input.sizes?.xs,
field: {
...Input.sizes?.xs.field,
...iconSpacing,
fontSize: 'sm',
lineHeight: '20px',
},
},
};
const Select = defineMultiStyleConfig({
variants: {
...Input.variants,
outline: variantOutline,
},
sizes,
defaultProps: {
size: 'xs',
},
});
export default Select;
......@@ -4,6 +4,10 @@ const semanticTokens = {
'default': 'blackAlpha.200',
_dark: 'whiteAlpha.200',
},
text_secondary: {
'default': 'gray.500',
_dark: 'gray.400',
},
link: {
'default': 'blue.600',
_dark: 'blue.300',
......@@ -11,6 +15,10 @@ const semanticTokens = {
link_hovered: {
'default': 'blue.400',
},
error: {
'default': 'red.400',
_dark: 'red.300',
},
},
};
......
......@@ -86,7 +86,7 @@ const ContractWriteResultDumb = ({ result, onSettle, txInfo }: Props) => {
alignItems="center"
whiteSpace="pre-wrap"
wordBreak="break-all"
color={ txInfo.status === 'error' || isErrorResult ? 'red.600' : undefined }
color={ txInfo.status === 'error' || isErrorResult ? 'error' : undefined }
>
{ content }
</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>
......
......@@ -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 }>
......
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import TestApp from 'playwright/TestApp';
import ContractVerificationForm from './ContractVerificationForm';
const hooksConfig = {
router: {
query: {},
},
};
test('flatten source code method +@dark-mode +@mobile', async({ mount, page }) => {
const component = await mount(
<TestApp>
<ContractVerificationForm/>
</TestApp>,
{ hooksConfig },
);
await page.getByText(/flattened source code/i).click();
await page.getByText(/optimization enabled/i).click();
await page.getByText(/add contract libraries/i).click();
await page.locator('button[aria-label="add"]').click();
await expect(component).toHaveScreenshot();
});
test('standard input json method', async({ mount, page }) => {
const component = await mount(
<TestApp>
<ContractVerificationForm/>
</TestApp>,
{ hooksConfig },
);
await page.getByText(/via standard/i).click();
await expect(component).toHaveScreenshot();
});
test('sourcify method +@dark-mode +@mobile', async({ mount, page }) => {
const component = await mount(
<TestApp>
<ContractVerificationForm/>
</TestApp>,
{ hooksConfig },
);
await page.getByText(/via sourcify/i).click();
await page.getByText(/upload files/i).click();
await page.locator('input[name="sources"]').setInputFiles([
'./playwright/mocks/file_mock_1.json',
'./playwright/mocks/file_mock_2.json',
'./playwright/mocks/file_mock_with_very_long_name.json',
]);
await expect(component).toHaveScreenshot();
});
test('multi-part files method', async({ mount, page }) => {
const component = await mount(
<TestApp>
<ContractVerificationForm/>
</TestApp>,
{ hooksConfig },
);
await page.getByText(/via multi-part files/i).click();
await expect(component).toHaveScreenshot();
});
test('vyper contract method', async({ mount, page }) => {
const component = await mount(
<TestApp>
<ContractVerificationForm/>
</TestApp>,
{ hooksConfig },
);
await page.getByText(/vyper contract/i).click();
await expect(component).toHaveScreenshot();
});
import { Button, chakra } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import type { SubmitHandler } from 'react-hook-form';
import { useForm, FormProvider } from 'react-hook-form';
import type { FormFields, VerificationMethod } from './types';
import delay from 'lib/delay';
import ContractVerificationFieldMethod, { VERIFICATION_METHODS } from './fields/ContractVerificationFieldMethod';
import ContractVerificationFlattenSourceCode from './methods/ContractVerificationFlattenSourceCode';
import ContractVerificationMultiPartFile from './methods/ContractVerificationMultiPartFile';
import ContractVerificationSourcify from './methods/ContractVerificationSourcify';
import ContractVerificationStandardInput from './methods/ContractVerificationStandardInput';
import ContractVerificationVyperContract from './methods/ContractVerificationVyperContract';
const METHODS = {
flatten_source_code: <ContractVerificationFlattenSourceCode/>,
standard_input: <ContractVerificationStandardInput/>,
sourcify: <ContractVerificationSourcify/>,
multi_part_file: <ContractVerificationMultiPartFile/>,
vyper_contract: <ContractVerificationVyperContract/>,
};
const ContractVerificationForm = () => {
const router = useRouter();
const methodFromQuery = router.query.method?.toString() as VerificationMethod;
const formApi = useForm<FormFields>({
mode: 'onBlur',
defaultValues: {
method: VERIFICATION_METHODS.includes(methodFromQuery) ? methodFromQuery : undefined,
},
});
const { control, handleSubmit, watch, formState } = formApi;
const onFormSubmit: SubmitHandler<FormFields> = React.useCallback(async(data) => {
// eslint-disable-next-line no-console
console.log('__>__', data);
await delay(5_000);
}, []);
const method = watch('method');
const content = METHODS[method] || null;
return (
<FormProvider { ...formApi }>
<chakra.form
noValidate
onSubmit={ handleSubmit(onFormSubmit) }
mt={ 12 }
>
<ContractVerificationFieldMethod control={ control } isDisabled={ Boolean(method) }/>
{ content }
{ Boolean(method) && (
<Button
variant="solid"
size="lg"
type="submit"
mt={ 12 }
isLoading={ formState.isSubmitting }
loadingText="Verify & publish"
>
Verify & publish
</Button>
) }
</chakra.form>
</FormProvider>
);
};
export default React.memo(ContractVerificationForm);
import { chakra, GridItem } from '@chakra-ui/react';
import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
interface Props {
children: [JSX.Element, JSX.Element | null] | (JSX.Element | null);
className?: string;
}
const ContractVerificationFormRow = ({ children, className }: Props) => {
const isMobile = useIsMobile();
const firstChildren = Array.isArray(children) ? children[0] : children;
const secondChildren = Array.isArray(children) ? children[1] : null;
return (
<>
<GridItem className={ className } _notFirst={{ mt: { base: 3, lg: 0 } }}>{ firstChildren }</GridItem>
{ isMobile && !secondChildren ? null : <GridItem fontSize="sm" className={ className } color="text_secondary">{ secondChildren }</GridItem> }
</>
);
};
export default React.memo(chakra(ContractVerificationFormRow));
import { Grid, Text } from '@chakra-ui/react';
import React from 'react';
interface Props {
title: string;
children: React.ReactNode;
}
const ContractVerificationMethod = ({ title, children }: Props) => {
const ref = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
ref.current?.scrollIntoView({ behavior: 'smooth' });
}, []);
return (
<section ref={ ref }>
<Text variant="secondary" mt={ 12 } mb={ 5 } fontSize="sm">{ title }</Text>
<Grid columnGap="30px" rowGap={{ base: 2, lg: 4 }} templateColumns={{ base: '1fr', lg: 'minmax(auto, 680px) minmax(0, 340px)' }}>
{ children }
</Grid>
</section>
);
};
export default React.memo(ContractVerificationMethod);
import { FormControl, Link, Textarea } from '@chakra-ui/react';
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { Controller, useFormContext } from 'react-hook-form';
import type { FormFields } from '../types';
import InputPlaceholder from 'ui/shared/InputPlaceholder';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
const ContractVerificationFieldAbiEncodedArgs = () => {
const { formState, control } = useFormContext<FormFields>();
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'abi_encoded_args'>}) => {
return (
<FormControl variant="floating" id={ field.name } size={{ base: 'md', lg: 'lg' }}>
<Textarea
{ ...field }
maxLength={ 255 }
isDisabled={ formState.isSubmitting }
/>
<InputPlaceholder text="ABI-encoded Constructor Arguments"/>
</FormControl>
);
}, [ formState.isSubmitting ]);
return (
<ContractVerificationFormRow>
<Controller
name="abi_encoded_args"
control={ control }
render={ renderControl }
/>
<>
<span>Add arguments in </span>
<Link href="https://solidity.readthedocs.io/en/develop/abi-spec.html" target="_blank">ABI hex encoded form</Link>
<span> if required by the contract. Constructor arguments are written right to left, and will be found at the end of the input created bytecode.</span>
<span> They may also be </span>
<Link href="https://abi.hashex.org/" target="_blank">parsed here</Link>
<span>.</span>
</>
</ContractVerificationFormRow>
);
};
export default React.memo(ContractVerificationFieldAbiEncodedArgs);
import { FormControl, Link, Textarea } from '@chakra-ui/react';
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { useFormContext, Controller } from 'react-hook-form';
import type { FormFields } from '../types';
import InputPlaceholder from 'ui/shared/InputPlaceholder';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
interface Props {
isVyper?: boolean;
}
const ContractVerificationFieldCode = ({ isVyper }: Props) => {
const { formState, control } = useFormContext<FormFields>();
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'code'>}) => {
const error = 'code' in formState.errors ? formState.errors.code : undefined;
return (
<FormControl variant="floating" id={ field.name } isRequired size={{ base: 'md', lg: 'lg' }}>
<Textarea
{ ...field }
isInvalid={ Boolean(error) }
isDisabled={ formState.isSubmitting }
required
/>
<InputPlaceholder text="Contract code" error={ error }/>
</FormControl>
);
}, [ formState.errors, formState.isSubmitting ]);
return (
<ContractVerificationFormRow>
<Controller
name="code"
control={ control }
render={ renderControl }
rules={{ required: true }}
defaultValue=""
/>
{ isVyper ? null : (
<>
<span>We recommend using flattened code. This is necessary if your code utilizes a library or inherits dependencies. Use the </span>
<Link href="https://github.com/poanetwork/solidity-flattener" target="_blank">POA solidity flattener</Link>
<span> or the </span>
<Link href="https://www.npmjs.com/package/truffle-flattener" target="_blank">Truffle flattener</Link>
</>
) }
</ContractVerificationFormRow>
);
};
export default React.memo(ContractVerificationFieldCode);
import { Code, Checkbox } from '@chakra-ui/react';
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { useFormContext, Controller } from 'react-hook-form';
import type { FormFields } from '../types';
import useIsMobile from 'lib/hooks/useIsMobile';
import FancySelect from 'ui/shared/FancySelect/FancySelect';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
const COMPILERS = [
'v0.8.17+commit.8df45f5f',
'v0.8.16+commit.07a7930e',
'v0.8.15+commit.e14f2714',
];
const COMPILERS_NIGHTLY = [
'v0.8.18-nightly.2022.11.23+commit.eb2f874e',
'v0.8.17-nightly.2022.8.24+commit.22a0c46e',
'v0.8.16-nightly.2022.7.6+commit.b6f11b33',
];
interface Props {
isVyper?: boolean;
}
const ContractVerificationFieldCompiler = ({ isVyper }: Props) => {
const [ isNightly, setIsNightly ] = React.useState(false);
const { formState, control, getValues, resetField } = useFormContext<FormFields>();
const isMobile = useIsMobile();
const options = React.useMemo(() => (
[
...COMPILERS, ...(isNightly ? COMPILERS_NIGHTLY : []),
].map((option) => ({ label: option, value: option }))
), [ isNightly ]);
const handleCheckboxChange = React.useCallback(() => {
if (isNightly) {
const field = getValues('compiler');
field.value.includes('nightly') && resetField('compiler', { defaultValue: null });
}
setIsNightly(prev => !prev);
}, [ getValues, isNightly, resetField ]);
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'compiler'>}) => {
const error = 'compiler' in formState.errors ? formState.errors.compiler : undefined;
return (
<FancySelect
{ ...field }
options={ options }
size={ isMobile ? 'md' : 'lg' }
placeholder="Compiler"
isDisabled={ formState.isSubmitting }
error={ error }
isRequired
/>
);
}, [ formState.errors, formState.isSubmitting, isMobile, options ]);
return (
<ContractVerificationFormRow>
<>
<Controller
name="compiler"
control={ control }
render={ renderControl }
rules={{ required: true }}
/>
{ !isVyper && (
<Checkbox
size="lg"
mt={ 3 }
onChange={ handleCheckboxChange }
isDisabled={ formState.isSubmitting }
>
Include nightly builds
</Checkbox>
) }
</>
{ isVyper ? null : (
<>
<span>The compiler version is specified in </span>
<Code color="text_secondary">pragma solidity X.X.X</Code>
<span>. Use the compiler version rather than the nightly build. If using the Solidity compiler, run </span>
<Code color="text_secondary">solc —version</Code>
<span> to check.</span>
</>
) }
</ContractVerificationFormRow>
);
};
export default React.memo(ContractVerificationFieldCompiler);
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { Controller, useFormContext } from 'react-hook-form';
import type { FormFields } from '../types';
import CheckboxInput from 'ui/shared/CheckboxInput';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
const ContractVerificationFieldConstArgs = () => {
const { formState, control } = useFormContext<FormFields>();
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'constructor_args'>}) => (
<CheckboxInput<FormFields, 'constructor_args'>
text="Try to fetch constructor arguments automatically"
field={ field }
isDisabled={ formState.isSubmitting }
/>
), [ formState.isSubmitting ]);
return (
<ContractVerificationFormRow>
<Controller
name="constructor_args"
control={ control }
render={ renderControl }
/>
</ContractVerificationFormRow>
);
};
export default React.memo(ContractVerificationFieldConstArgs);
import { Link } from '@chakra-ui/react';
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { useFormContext, Controller } from 'react-hook-form';
import type { FormFields } from '../types';
import useIsMobile from 'lib/hooks/useIsMobile';
import FancySelect from 'ui/shared/FancySelect/FancySelect';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
const VERSIONS = [
'default',
'london',
'berlin',
];
const ContractVerificationFieldEvmVersion = () => {
const { formState, control } = useFormContext<FormFields>();
const isMobile = useIsMobile();
const options = React.useMemo(() => (
VERSIONS.map((option) => ({ label: option, value: option }))
), [ ]);
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'evm_version'>}) => {
const error = 'evm_version' in formState.errors ? formState.errors.evm_version : undefined;
return (
<FancySelect
{ ...field }
options={ options }
size={ isMobile ? 'md' : 'lg' }
placeholder="EVM Version"
isDisabled={ formState.isSubmitting }
error={ error }
isRequired
/>
);
}, [ formState.errors, formState.isSubmitting, isMobile, options ]);
return (
<ContractVerificationFormRow>
<Controller
name="evm_version"
control={ control }
render={ renderControl }
rules={{ required: true }}
/>
<>
<span>The EVM version the contract is written for. If the bytecode does not match the version, we try to verify using the latest EVM version. </span>
<Link href="https://forum.poa.network/t/smart-contract-verification-evm-version-details/2318" target="_blank">EVM version details</Link>
</>
</ContractVerificationFormRow>
);
};
export default React.memo(ContractVerificationFieldEvmVersion);
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { useFormContext, Controller } from 'react-hook-form';
import type { FormFields } from '../types';
import CheckboxInput from 'ui/shared/CheckboxInput';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
const ContractVerificationFieldIsYul = () => {
const { formState, control } = useFormContext<FormFields>();
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'is_yul'>}) => (
<CheckboxInput<FormFields, 'is_yul'> text="Is Yul contract" field={ field } isDisabled={ formState.isSubmitting }/>
), [ formState.isSubmitting ]);
return (
<ContractVerificationFormRow>
<Controller
name="is_yul"
control={ control }
render={ renderControl }
/>
</ContractVerificationFormRow>
);
};
export default React.memo(ContractVerificationFieldIsYul);
import { Checkbox } from '@chakra-ui/react';
import React from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';
import type { FormFields } from '../types';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
import ContractVerificationFieldLibraryItem from './ContractVerificationFieldLibraryItem';
const ContractVerificationFieldLibraries = () => {
const { formState, control } = useFormContext<FormFields>();
const { fields, append, remove, insert } = useFieldArray({
name: 'libraries',
control,
});
const [ isEnabled, setIsEnabled ] = React.useState(fields.length > 0);
const handleCheckboxChange = React.useCallback(() => {
if (!isEnabled) {
append({ name: '', address: '' });
} else {
remove();
}
setIsEnabled(prev => !prev);
}, [ append, isEnabled, remove ]);
const handleAddFieldClick = React.useCallback((index: number) => {
insert(index + 1, { name: '', address: '' });
}, [ insert ]);
const handleRemoveFieldClick = React.useCallback((index: number) => {
remove(index);
}, [ remove ]);
return (
<>
<ContractVerificationFormRow>
<Checkbox
size="lg"
onChange={ handleCheckboxChange }
mt={ 9 }
isDisabled={ formState.isSubmitting }
>
Add contract libraries
</Checkbox>
</ContractVerificationFormRow>
{ fields.map((field, index) => (
<ContractVerificationFieldLibraryItem
key={ field.id }
index={ index }
control={ control }
fieldsLength={ fields.length }
onAddFieldClick={ handleAddFieldClick }
onRemoveFieldClick={ handleRemoveFieldClick }
error={ 'libraries' in formState.errors ? formState.errors.libraries?.[index] : undefined }
isDisabled={ formState.isSubmitting }
/>
)) }
</>
);
};
export default React.memo(ContractVerificationFieldLibraries);
import { Flex, FormControl, Icon, IconButton, Input, Text } from '@chakra-ui/react';
import React from 'react';
import type { Control, ControllerRenderProps, FieldError } from 'react-hook-form';
import { Controller } from 'react-hook-form';
import type { FormFields } from '../types';
import minusIcon from 'icons/minus.svg';
import plusIcon from 'icons/plus.svg';
import { ADDRESS_REGEXP } from 'lib/validations/address';
import InputPlaceholder from 'ui/shared/InputPlaceholder';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
const LIMIT = 10;
interface Props {
control: Control<FormFields>;
index: number;
fieldsLength: number;
error?: {
name?: FieldError;
address?: FieldError;
};
onAddFieldClick: (index: number) => void;
onRemoveFieldClick: (index: number) => void;
isDisabled?: boolean;
}
const ContractVerificationFieldLibraryItem = ({ control, index, fieldsLength, onAddFieldClick, onRemoveFieldClick, error, isDisabled }: Props) => {
const ref = React.useRef<HTMLDivElement>(null);
const renderNameControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, `libraries.${ number }.name`>}) => {
return (
<FormControl variant="floating" id={ field.name } isRequired size={{ base: 'md', lg: 'lg' }}>
<Input
{ ...field }
required
isInvalid={ Boolean(error?.name) }
isDisabled={ isDisabled }
maxLength={ 255 }
autoComplete="off"
/>
<InputPlaceholder text="Library name (.sol file)" error={ error?.name }/>
</FormControl>
);
}, [ error?.name, isDisabled ]);
const renderAddressControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, `libraries.${ number }.address`>}) => {
return (
<FormControl variant="floating" id={ field.name } isRequired size={{ base: 'md', lg: 'lg' }}>
<Input
{ ...field }
isInvalid={ Boolean(error?.address) }
isDisabled={ isDisabled }
required
autoComplete="off"
/>
<InputPlaceholder text="Library address (0x...)" error={ error?.address }/>
</FormControl>
);
}, [ error?.address, isDisabled ]);
const handleAddButtonClick = React.useCallback(() => {
onAddFieldClick(index);
}, [ index, onAddFieldClick ]);
const handleRemoveButtonClick = React.useCallback(() => {
onRemoveFieldClick(index);
}, [ index, onRemoveFieldClick ]);
React.useEffect(() => {
ref.current?.scrollIntoView({ behavior: 'smooth' });
}, []);
return (
<>
<ContractVerificationFormRow>
<Flex alignItems="center" justifyContent="space-between" ref={ ref } mt={ index !== 0 ? 6 : 0 }>
<Text variant="secondary" fontSize="sm">Contract library { index + 1 }</Text>
<Flex columnGap={ 5 }>
{ fieldsLength > 1 && (
<IconButton
aria-label="delete"
variant="outline"
w="30px"
h="30px"
onClick={ handleRemoveButtonClick }
icon={ <Icon as={ minusIcon } w="20px" h="20px"/> }
isDisabled={ isDisabled }
/>
) }
{ fieldsLength < LIMIT && (
<IconButton
aria-label="add"
variant="outline"
w="30px"
h="30px"
onClick={ handleAddButtonClick }
icon={ <Icon as={ plusIcon } w="20px" h="20px"/> }
isDisabled={ isDisabled }
/>
) }
</Flex>
</Flex>
</ContractVerificationFormRow>
<ContractVerificationFormRow>
<Controller
name={ `libraries.${ index }.name` }
control={ control }
render={ renderNameControl }
rules={{ required: true }}
/>
{ index === 0 ? (
<>
A library name called in the .sol file. Multiple libraries (up to 10) may be added for each contract.
</>
) : null }
</ContractVerificationFormRow>
<ContractVerificationFormRow>
<Controller
name={ `libraries.${ index }.address` }
control={ control }
render={ renderAddressControl }
rules={{ required: true, pattern: ADDRESS_REGEXP }}
/>
{ index === 0 ? (
<>
The 0x library address. This can be found in the generated json file or Truffle output (if using truffle).
</>
) : null }
</ContractVerificationFormRow>
</>
);
};
export default React.memo(ContractVerificationFieldLibraryItem);
import {
RadioGroup,
Radio,
Stack,
Text,
Link,
Icon,
chakra,
Popover,
PopoverTrigger,
Portal,
PopoverContent,
PopoverArrow,
PopoverBody,
useColorModeValue,
DarkMode,
useBoolean,
} from '@chakra-ui/react';
import React from 'react';
import type { ControllerRenderProps, Control } from 'react-hook-form';
import { Controller } from 'react-hook-form';
import type { FormFields, VerificationMethod } from '../types';
import infoIcon from 'icons/info.svg';
export const VERIFICATION_METHODS: Array<VerificationMethod> = [
'flatten_source_code',
'standard_input',
'sourcify',
'multi_part_file',
'vyper_contract',
];
interface Props {
control: Control<FormFields>;
isDisabled?: boolean;
}
const ContractVerificationFieldMethod = ({ control, isDisabled }: Props) => {
const [ isPopoverOpen, setIsPopoverOpen ] = useBoolean();
const tooltipBg = useColorModeValue('gray.700', 'gray.900');
const renderItem = React.useCallback((method: VerificationMethod) => {
switch (method) {
case 'flatten_source_code':
return 'Via flattened source code';
case 'standard_input':
return (
<>
<span>Via standard </span>
<Link
href={ isDisabled ? undefined : 'https://docs.soliditylang.org/en/latest/using-the-compiler.html#input-description' }
target="_blank"
cursor={ isDisabled ? 'not-allowed' : 'pointer' }
>
Input JSON
</Link>
</>
);
case 'sourcify':
return (
<>
<span>Via sourcify: sources and metadata JSON file</span>
<Popover trigger="hover" isLazy isOpen={ isDisabled ? false : isPopoverOpen } onOpen={ setIsPopoverOpen.on } onClose={ setIsPopoverOpen.off }>
<PopoverTrigger>
<chakra.span cursor={ isDisabled ? 'not-allowed' : 'pointer' } display="inline-block" verticalAlign="middle" h="24px" ml={ 1 }>
<Icon as={ infoIcon } boxSize={ 5 } color="link" _hover={{ color: 'link_hovered' }}/>
</chakra.span>
</PopoverTrigger>
<Portal>
<PopoverContent bgColor={ tooltipBg }>
<PopoverArrow bgColor={ tooltipBg }/>
<PopoverBody color="white">
<DarkMode>
<div>
<span>Verification through </span>
<Link href="https://sourcify.dev/" target="_blank">Sourcify</Link>
</div>
<div>
<span>a) if smart-contract already verified on Sourcify, it will automatically fetch the data from the </span>
<Link href="https://repo.sourcify.dev/" target="_blank">repo</Link>
</div>
<div>
b) otherwise you will be asked to upload source files and JSON metadata file(s).
</div>
</DarkMode>
</PopoverBody>
</PopoverContent>
</Portal>
</Popover>
</>
);
case 'multi_part_file':
return 'Via multi-part files';
case 'vyper_contract':
return 'Vyper contract';
default:
break;
}
}, [ isDisabled, isPopoverOpen, setIsPopoverOpen.off, setIsPopoverOpen.on, tooltipBg ]);
const renderRadioGroup = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'method'>}) => {
return (
<RadioGroup defaultValue="add" colorScheme="blue" isDisabled={ isDisabled } { ...field }>
<Stack spacing={ 4 }>
{ VERIFICATION_METHODS.map((method) => {
return <Radio key={ method } value={ method } size="lg">{ renderItem(method) }</Radio>;
}) }
</Stack>
</RadioGroup>
);
}, [ isDisabled, renderItem ]);
return (
<section>
<Text variant="secondary" fontSize="sm" mb={ 5 }>Smart-contract verification method</Text>
<Controller
name="method"
control={ control }
render={ renderRadioGroup }
/>
</section>
);
};
export default React.memo(ContractVerificationFieldMethod);
import { chakra, Code, FormControl, Input } from '@chakra-ui/react';
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { Controller, useFormContext } from 'react-hook-form';
import type { FormFields } from '../types';
import InputPlaceholder from 'ui/shared/InputPlaceholder';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
interface Props {
hint?: string;
}
const ContractVerificationFieldName = ({ hint }: Props) => {
const { formState, control } = useFormContext<FormFields>();
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'name'>}) => {
const error = 'name' in formState.errors ? formState.errors.name : undefined;
return (
<FormControl variant="floating" id={ field.name } isRequired size={{ base: 'md', lg: 'lg' }}>
<Input
{ ...field }
required
isInvalid={ Boolean(error) }
maxLength={ 255 }
isDisabled={ formState.isSubmitting }
autoComplete="off"
/>
<InputPlaceholder text="Contract name" error={ error }/>
</FormControl>
);
}, [ formState.errors, formState.isSubmitting ]);
return (
<ContractVerificationFormRow>
<Controller
name="name"
control={ control }
render={ renderControl }
rules={{ required: true }}
defaultValue=""
/>
{ hint ? <span>{ hint }</span> : (
<>
<span>Must match the name specified in the code. For example, in </span>
<Code color="text_secondary">{ `contract MyContract {..}` }</Code>
<span>. <chakra.span fontWeight={ 600 }>MyContract</chakra.span> is the contract name.</span>
</>
) }
</ContractVerificationFormRow>
);
};
export default React.memo(ContractVerificationFieldName);
import { FormControl, Input } from '@chakra-ui/react';
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { Controller, useFormContext } from 'react-hook-form';
import type { FormFields } from '../types';
import CheckboxInput from 'ui/shared/CheckboxInput';
import InputPlaceholder from 'ui/shared/InputPlaceholder';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
const ContractVerificationFieldOptimization = () => {
const [ isEnabled, setIsEnabled ] = React.useState(false);
const { formState, control } = useFormContext<FormFields>();
const handleCheckboxChange = React.useCallback(() => {
setIsEnabled(prev => !prev);
}, []);
const renderCheckboxControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'is_optimization_enabled'>}) => (
<CheckboxInput<FormFields, 'is_optimization_enabled'>
text="Optimization enabled"
field={ field }
onChange={ handleCheckboxChange }
isDisabled={ formState.isSubmitting }
/>
), [ formState.isSubmitting, handleCheckboxChange ]);
const renderInputControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'optimization_runs'>}) => {
return (
<FormControl variant="floating" id={ field.name } size={{ base: 'md', lg: 'lg' }} isRequired>
<Input
{ ...field }
required
isDisabled={ formState.isSubmitting }
autoComplete="off"
type="number"
/>
<InputPlaceholder text="Optimization runs"/>
</FormControl>
);
}, [ formState.isSubmitting ]);
return (
<>
<ContractVerificationFormRow>
<Controller
name="is_optimization_enabled"
control={ control }
render={ renderCheckboxControl }
/>
</ContractVerificationFormRow>
{ isEnabled && (
<ContractVerificationFormRow>
<Controller
name="optimization_runs"
control={ control }
render={ renderInputControl }
rules={{ required: true }}
defaultValue=""
/>
</ContractVerificationFormRow>
) }
</>
);
};
export default React.memo(ContractVerificationFieldOptimization);
import { Text, Button, Box, chakra } from '@chakra-ui/react';
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { Controller, useFormContext } from 'react-hook-form';
import type { FormFields } from '../types';
import FileInput from 'ui/shared/forms/FileInput';
import FileSnippet from 'ui/shared/forms/FileSnippet';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
interface Props {
accept?: string;
multiple?: boolean;
title: string;
className?: string;
hint: string;
}
const ContractVerificationFieldSources = ({ accept, multiple, title, className, hint }: Props) => {
const { setValue, getValues, control, formState } = useFormContext<FormFields>();
const error = 'sources' in formState.errors ? formState.errors.sources : undefined;
const handleFileRemove = React.useCallback((index?: number) => {
if (index === undefined) {
return;
}
const value = getValues('sources').slice();
value.splice(index, 1);
setValue('sources', value);
}, [ getValues, setValue ]);
const renderFiles = React.useCallback((files: Array<File>) => {
return (
<Box display="grid" gridTemplateColumns={{ base: '1fr', lg: '1fr 1fr' }} columnGap={ 3 } rowGap={ 3 }>
{ files.map((file, index) => (
<FileSnippet
key={ file.name + file.lastModified }
file={ file }
maxW="initial"
onRemove={ handleFileRemove }
index={ index }
isDisabled={ formState.isSubmitting }
/>
)) }
</Box>
);
}, [ formState.isSubmitting, handleFileRemove ]);
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'sources'>}) => (
<>
<FileInput<FormFields, 'sources'> accept={ accept } multiple={ multiple } field={ field }>
<Button variant="outline" size="sm" display={ field.value && field.value.length > 0 ? 'none' : 'block' }>
Upload file{ multiple ? 's' : '' }
</Button>
</FileInput>
{ field.value && field.value.length > 0 && renderFiles(field.value) }
{ error && (
<Box fontSize="sm" mt={ 2 } color="error">
{ error.type === 'required' ? 'Field is required' : error.message }
</Box>
) }
</>
), [ accept, error, multiple, renderFiles ]);
return (
<>
<ContractVerificationFormRow >
<Text fontWeight={ 500 } className={ className } mt={ 4 }>{ title }</Text>
</ContractVerificationFormRow>
<ContractVerificationFormRow>
<Controller
name="sources"
control={ control }
render={ renderControl }
rules={{ required: true }}
/>
{ hint ? <span>{ hint }</span> : null }
</ContractVerificationFormRow>
</>
);
};
export default React.memo(chakra(ContractVerificationFieldSources));
import React from 'react';
import ContractVerificationMethod from '../ContractVerificationMethod';
import ContractVerificationFieldCode from '../fields/ContractVerificationFieldCode';
import ContractVerificationFieldCompiler from '../fields/ContractVerificationFieldCompiler';
import ContractVerificationFieldConstArgs from '../fields/ContractVerificationFieldConstArgs';
import ContractVerificationFieldEvmVersion from '../fields/ContractVerificationFieldEvmVersion';
import ContractVerificationFieldIsYul from '../fields/ContractVerificationFieldIsYul';
import ContractVerificationFieldLibraries from '../fields/ContractVerificationFieldLibraries';
import ContractVerificationFieldName from '../fields/ContractVerificationFieldName';
import ContractVerificationFieldOptimization from '../fields/ContractVerificationFieldOptimization';
const ContractVerificationFlattenSourceCode = () => {
return (
<ContractVerificationMethod title="New Solidity/Yul Smart Contract Verification">
<ContractVerificationFieldIsYul/>
<ContractVerificationFieldName/>
<ContractVerificationFieldCompiler/>
<ContractVerificationFieldEvmVersion/>
<ContractVerificationFieldOptimization/>
<ContractVerificationFieldCode/>
<ContractVerificationFieldConstArgs/>
<ContractVerificationFieldLibraries/>
</ContractVerificationMethod>
);
};
export default React.memo(ContractVerificationFlattenSourceCode);
import React from 'react';
import ContractVerificationMethod from '../ContractVerificationMethod';
import ContractVerificationFieldCompiler from '../fields/ContractVerificationFieldCompiler';
import ContractVerificationFieldEvmVersion from '../fields/ContractVerificationFieldEvmVersion';
import ContractVerificationFieldLibraries from '../fields/ContractVerificationFieldLibraries';
import ContractVerificationFieldOptimization from '../fields/ContractVerificationFieldOptimization';
import ContractVerificationFieldSources from '../fields/ContractVerificationFieldSources';
const ContractVerificationMultiPartFile = () => {
return (
<ContractVerificationMethod title="New Solidity/Yul Smart Contract Verification">
<ContractVerificationFieldCompiler/>
<ContractVerificationFieldEvmVersion/>
<ContractVerificationFieldOptimization/>
<ContractVerificationFieldSources
accept=".sol,.yul"
multiple
title="Sources *.sol or *.yul files"
hint="Upload all Solidity or Yul contract source files."
/>
<ContractVerificationFieldLibraries/>
</ContractVerificationMethod>
);
};
export default React.memo(ContractVerificationMultiPartFile);
import React from 'react';
import ContractVerificationMethod from '../ContractVerificationMethod';
import ContractVerificationFieldSources from '../fields/ContractVerificationFieldSources';
const ContractVerificationSourcify = () => {
return (
<ContractVerificationMethod title="New Smart Contract Verification">
<ContractVerificationFieldSources
accept=".json"
multiple
title="Sources and Metadata JSON" mt={ 0 }
hint="Upload all Solidity contract source files and JSON metadata file(s) created during contract compilation."
/>
</ContractVerificationMethod>
);
};
export default React.memo(ContractVerificationSourcify);
import React from 'react';
import ContractVerificationMethod from '../ContractVerificationMethod';
import ContractVerificationFieldCompiler from '../fields/ContractVerificationFieldCompiler';
import ContractVerificationFieldConstArgs from '../fields/ContractVerificationFieldConstArgs';
import ContractVerificationFieldName from '../fields/ContractVerificationFieldName';
import ContractVerificationFieldSources from '../fields/ContractVerificationFieldSources';
const ContractVerificationStandardInput = () => {
return (
<ContractVerificationMethod title="New Smart Contract Verification">
<ContractVerificationFieldName/>
<ContractVerificationFieldCompiler/>
<ContractVerificationFieldSources
accept=".json"
title="Standard Input JSON"
hint="Upload the standard input JSON file created during contract compilation."
/>
<ContractVerificationFieldConstArgs/>
</ContractVerificationMethod>
);
};
export default React.memo(ContractVerificationStandardInput);
import React from 'react';
import ContractVerificationMethod from '../ContractVerificationMethod';
import ContractVerificationFieldAbiEncodedArgs from '../fields/ContractVerificationFieldAbiEncodedArgs';
import ContractVerificationFieldCode from '../fields/ContractVerificationFieldCode';
import ContractVerificationFieldCompiler from '../fields/ContractVerificationFieldCompiler';
import ContractVerificationFieldName from '../fields/ContractVerificationFieldName';
const ContractVerificationVyperContract = () => {
return (
<ContractVerificationMethod title="New Vyper Smart Contract Verification">
<ContractVerificationFieldName hint="Must match the name specified in the code."/>
<ContractVerificationFieldCompiler isVyper/>
<ContractVerificationFieldCode isVyper/>
<ContractVerificationFieldAbiEncodedArgs/>
</ContractVerificationMethod>
);
};
export default React.memo(ContractVerificationVyperContract);
import type { Option } from 'ui/shared/FancySelect/types';
export type VerificationMethod = 'flatten_source_code' | 'standard_input' | 'sourcify' | 'multi_part_file' | 'vyper_contract'
export interface ContractLibrary {
name: string;
address: string;
}
export interface FormFieldsFlattenSourceCode {
method: 'flatten_source_code';
is_yul: boolean;
name: string;
compiler: Option;
evm_version: Option;
is_optimization_enabled: boolean;
optimization_runs: string;
code: string;
constructor_args: boolean;
libraries: Array<ContractLibrary>;
}
export interface FormFieldsStandardInput {
method: 'standard_input';
name: string;
compiler: Option;
sources: Array<File>;
}
export interface FormFieldsSourcify {
method: 'sourcify';
sources: Array<File>;
}
export interface FormFieldsMultiPartFile {
method: 'multi_part_file';
compiler: Option;
evm_version: Option;
is_optimization_enabled: boolean;
optimization_runs: string;
sources: Array<File>;
}
export interface FormFieldsVyperContract {
method: 'vyper_contract';
name: string;
compiler: Option;
code: string;
abi_encoded_args: string;
}
export type FormFields = FormFieldsFlattenSourceCode | FormFieldsStandardInput | FormFieldsSourcify |
FormFieldsMultiPartFile | FormFieldsVyperContract;
import { Text } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import { useAppContext } from 'lib/appContext';
import isBrowser from 'lib/isBrowser';
import link from 'lib/link/link';
import ContractVerificationForm from 'ui/contractVerification/ContractVerificationForm';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
const ContractVerification = () => {
const appProps = useAppContext();
const isInBrowser = isBrowser();
const referrer = isInBrowser ? window.document.referrer : appProps.referrer;
const hasGoBackLink = referrer && referrer.includes('/address');
const router = useRouter();
const hash = router.query.id?.toString();
const method = router.query.id?.toString();
React.useEffect(() => {
if (method && hash) {
router.replace(link('address_contract_verification', { id: hash }), undefined, { scroll: false, shallow: true });
}
// onMount only
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ ]);
return (
<Page>
<PageTitle
text="New smart contract verification"
backLinkUrl={ hasGoBackLink ? referrer : undefined }
backLinkLabel="Back to contract"
/>
{ hash && (
<Address>
<AddressIcon address={{ hash, is_contract: true, implementation_name: null }} flexShrink={ 0 }/>
<Text fontFamily="heading" ml={ 2 } fontWeight={ 500 } fontSize="lg" w={{ base: '100%', lg: 'auto' }} whiteSpace="nowrap" overflow="hidden">
<HashStringShortenDynamic hash={ hash }/>
</Text>
<CopyToClipboard text={ hash }/>
</Address>
) }
<ContractVerificationForm/>
</Page>
);
};
export default ContractVerification;
......@@ -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={ (
......
......@@ -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">
......
......@@ -7,20 +7,31 @@ import type { ControllerRenderProps, FieldValues, Path } from 'react-hook-form';
type Props<TInputs extends FieldValues, TInputName extends Path<TInputs>> = {
field: ControllerRenderProps<TInputs, TInputName>;
text: string;
onChange?: () => void;
isDisabled?: boolean;
}
export default function CheckboxInput<Inputs extends FieldValues, Name extends Path<Inputs>>(
{
field,
text,
onChange,
isDisabled,
}: Props<Inputs, Name>) {
const handleChange: typeof field.onChange = React.useCallback((...args) => {
field.onChange(...args);
onChange?.();
}, [ field, onChange ]);
return (
<Checkbox
isChecked={ field.value }
onChange={ field.onChange }
onChange={ handleChange }
ref={ field.ref }
colorScheme="blue"
size="lg"
isDisabled={ isDisabled }
>
{ text }
</Checkbox>
......
import { test, expect } from '@playwright/experimental-ct-react';
import _noop from 'lodash/noop';
import React from 'react';
import TestApp from 'playwright/TestApp';
import FancySelect from './FancySelect';
const OPTIONS = [
{ value: 'v0.8.17+commit.8df45f5f', label: 'v0.8.17+commit.8df45f5f' },
{ value: 'v0.8.16+commit.07a7930e', label: 'v0.8.16+commit.07a7930e' },
{ value: 'v0.8.15+commit.e14f2714', label: 'v0.8.15+commit.e14f2714' },
];
test.use({ viewport: { width: 500, height: 300 } });
const defaultProps = {
options: OPTIONS,
isRequired: true,
placeholder: 'Compiler',
name: 'compiler',
onChange: _noop,
};
[ 'md' as const, 'lg' as const ].forEach((size) => {
test.describe(`size ${ size } +@dark-mode`, () => {
test('empty', async({ mount, page }) => {
const component = await mount(
<TestApp>
<FancySelect
{ ...defaultProps }
size={ size }
value={ null }
/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
await component.getByLabel(/compiler/i).focus();
await component.getByLabel(/compiler/i).type('1');
await expect(page).toHaveScreenshot();
});
test('filled', async({ mount }) => {
const component = await mount(
<TestApp>
<FancySelect
{ ...defaultProps }
size={ size }
value={ OPTIONS[0] }
/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
test('error', async({ mount }) => {
const component = await mount(
<TestApp>
<FancySelect
{ ...defaultProps }
size={ size }
value={ null }
error={{
type: 'unknown',
message: 'cannot be empty',
}}
/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
await component.getByLabel(/compiler/i).focus();
await component.getByLabel(/compiler/i).type('1');
await expect(component).toHaveScreenshot();
});
test('disabled', async({ mount }) => {
const component = await mount(
<TestApp>
<FancySelect
{ ...defaultProps }
size={ size }
value={ OPTIONS[0] }
isDisabled
/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
});
});
import { FormControl, useToken, useColorMode } from '@chakra-ui/react';
import type { Size, CSSObjectWithLabel, OptionsOrGroups, GroupBase, SingleValue, MultiValue } from 'chakra-react-select';
import { Select } from 'chakra-react-select';
import React from 'react';
import type { FieldError, FieldErrorsImpl, Merge } from 'react-hook-form';
import type { Option } from './types';
import { getChakraStyles } from 'ui/shared/FancySelect/utils';
import InputPlaceholder from 'ui/shared/InputPlaceholder';
interface Props {
size?: Size; // we don't have styles for sm select/input with floating label yet
options: OptionsOrGroups<Option, GroupBase<Option>>;
placeholder: string;
name: string;
onChange: (newValue: SingleValue<Option> | MultiValue<Option>) => void;
onBlur?: () => void;
isDisabled?: boolean;
isRequired?: boolean;
error?: Merge<FieldError, FieldErrorsImpl<Option>> | undefined;
value?: SingleValue<Option> | MultiValue<Option>;
}
const FancySelect = ({ size = 'md', options, placeholder, name, onChange, onBlur, isDisabled, isRequired, error, value }: Props) => {
const menuZIndex = useToken('zIndices', 'dropdown');
const { colorMode } = useColorMode();
const styles = React.useMemo(() => ({
menuPortal: (provided: CSSObjectWithLabel) => ({ ...provided, zIndex: menuZIndex }),
}), [ menuZIndex ]);
const chakraStyles = React.useMemo(() => getChakraStyles(colorMode), [ colorMode ]);
return (
<FormControl
variant="floating"
size={ size }
isRequired={ isRequired }
{ ...(error ? { 'aria-invalid': true } : {}) }
{ ...(isDisabled ? { 'aria-disabled': true } : {}) }
{ ...(value ? { 'data-active': true } : {}) }
>
<Select
menuPortalTarget={ window.document.body }
placeholder=""
name={ name }
options={ options }
size={ size }
styles={ styles }
chakraStyles={ chakraStyles }
useBasicStyles
onChange={ onChange }
onBlur={ onBlur }
isDisabled={ isDisabled }
isRequired={ isRequired }
isInvalid={ Boolean(error) }
value={ value }
/>
<InputPlaceholder
text={ placeholder }
error={ error }
isFancy
/>
</FormControl>
);
};
export default React.memo(React.forwardRef(FancySelect));
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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