├── .env ├── .env.development ├── .env.production ├── .github └── workflows │ ├── build.yml │ └── main.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierrc.json ├── LICENSE ├── index.html ├── package-lock.json ├── package.json ├── public ├── tonconnect-manifest.json ├── tonverifier.png ├── tree-sitter-func.wasm └── tree-sitter.wasm ├── readme.md ├── src ├── App.css ├── App.tsx ├── assets │ ├── chrome.svg │ ├── close.svg │ ├── compiler.svg │ ├── contract.svg │ ├── copy.svg │ ├── delete.svg │ ├── dnd.svg │ ├── download.svg │ ├── drag-handle.svg │ ├── file.svg │ ├── folder.svg │ ├── github-dark.svg │ ├── github-footer.svg │ ├── github-hover.svg │ ├── github.svg │ ├── heart.svg │ ├── icon.svg │ ├── info.svg │ ├── light-bulb.svg │ ├── like.svg │ ├── orbs.svg │ ├── publish.svg │ ├── qr.svg │ ├── react.svg │ ├── recent-search.svg │ ├── reorder-hint.svg │ ├── search.svg │ ├── show.svg │ ├── sources.svg │ ├── telegram-hover.svg │ ├── telegram.svg │ ├── tonhub-qr.png │ ├── tonhub.png │ ├── tonkeeper.png │ ├── tonverifier.ico │ ├── tonverifier.png │ ├── undo.svg │ ├── upload.svg │ ├── verification-alert.svg │ ├── verification-binary.svg │ ├── verification-bomb.svg │ ├── verification-paper.svg │ ├── verification-pen.svg │ ├── verification-popup.svg │ ├── verification.svg │ ├── verified-bold.svg │ ├── verified-light.svg │ ├── verified.svg │ └── verify_icon_2.svg ├── components │ ├── AddSourcesBlock.tsx │ ├── AddressInput.tsx │ ├── AppButton.tsx │ ├── AppNotification.tsx │ ├── AppPopup.tsx │ ├── Button.css │ ├── Button.tsx │ ├── Common.styled.tsx │ ├── CompileOutput.tsx │ ├── CompilerBlock.tsx │ ├── CompilerSetting.styled.tsx │ ├── CompilerSettings.tsx │ ├── ConnectButton.tsx │ ├── ConnectorHeader.tsx │ ├── ContractBlock.tsx │ ├── ContractSourceCode.css │ ├── ContractSourceCode.tsx │ ├── DataBlock.styled.tsx │ ├── DataBlock.tsx │ ├── DevExamples.tsx │ ├── DisassembledSourceCode.tsx │ ├── FileTable.styled.tsx │ ├── FileTable.tsx │ ├── FileUploaderArea.tsx │ ├── Footer.styled.tsx │ ├── Footer.tsx │ ├── Getters.styled.tsx │ ├── Getters.tsx │ ├── HintItem.tsx │ ├── HoverableIcon.tsx │ ├── InfoPiece.css │ ├── InfoPiece.tsx │ ├── LatestVerifiedContracts.tsx │ ├── MobileMenu.tsx │ ├── PublishProof.tsx │ ├── SearchResults.tsx │ ├── SearchRusults.styled.tsx │ ├── Spacer.tsx │ ├── TestnetBar.tsx │ ├── TopBar.styled.tsx │ ├── TopBar.tsx │ ├── VerificationGuides.tsx │ ├── VerificationInfoBlock.tsx │ ├── VerificationProof.tsx │ ├── VerificationProofPopup.styled.tsx │ ├── VerificationProofPopup.tsx │ ├── VerificationProofPopupTable.tsx │ ├── VerificationProofTable.tsx │ ├── VerifiedSourceCode.tsx │ ├── admin │ │ ├── Admin.tsx │ │ ├── ContractInteract.tsx │ │ ├── SourcesRegistry.tsx │ │ ├── VerifierRegistry.tsx │ │ └── form │ │ │ └── TextField.tsx │ ├── tactDeployer │ │ ├── TactDeployer.styled.tsx │ │ ├── TactDeployer.tsx │ │ └── TopBar.tsx │ └── usePublishStepsStore.ts ├── const.ts ├── hooks.ts ├── index.css ├── lib │ ├── downloadSources.ts │ ├── getAdmin.ts │ ├── getClient.ts │ ├── getter │ │ ├── getterParser.ts │ │ ├── useCustomGetter.ts │ │ ├── useGetters.ts │ │ └── useQueryGetter.ts │ ├── googleAnalytics.ts │ ├── makeGetCall.ts │ ├── useAddressHistory.ts │ ├── useAddressInput.ts │ ├── useCompilerSettingsStore.ts │ ├── useContractAddress.ts │ ├── useCustomMutation.ts │ ├── useFileStore.tsx │ ├── useHover.ts │ ├── useInBrowserCompilation.ts │ ├── useLoadContractInfo.ts │ ├── useLoadContractProof.ts │ ├── useLoadContractSourceCode.ts │ ├── useLoadLatestVerified.ts │ ├── useLoadSourcesRegistryInfo.tsx │ ├── useLoadVerifierRegistryInfo.tsx │ ├── useNavigatePreserveQuery.ts │ ├── useNotification.tsx │ ├── useOverride.ts │ ├── usePublishProof.ts │ ├── usePublishSteps.ts │ ├── useRemoteConfig.ts │ ├── useResetState.ts │ ├── useSendTxn.ts │ ├── useSubmitSources.ts │ ├── workchainForAddress.ts │ └── wrappers │ │ ├── sources-registry.ts │ │ └── verifier-registry.ts ├── main.tsx ├── polyfills.ts ├── styles.ts ├── theme.ts ├── utils.ts ├── utils │ ├── generalUtils.ts │ ├── jsonUtils.ts │ ├── linkUtils.ts │ ├── numberUtils.ts │ └── textUtils.ts └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.env: -------------------------------------------------------------------------------- 1 | VITE_SHOW_MANUAL_VERIFICATION=true 2 | 3 | VITE_VERIFIER_ID=orbs.com 4 | VITE_BACKEND_URL=https://ton-source-prod-1.herokuapp.com,https://ton-source-prod-2.herokuapp.com,https://ton-source-prod-3.herokuapp.com 5 | VITE_SOURCES_REGISTRY=EQD-BJSVUJviud_Qv7Ymfd3qzXdrmV525e3YDzWQoHIAiInL 6 | 7 | VITE_VERIFIER_ID_TESTNET=orbs-testnet 8 | VITE_BACKEND_URL_TESTNET=https://ton-source-prod-testnet-1.herokuapp.com 9 | VITE_SOURCES_REGISTRY_TESTNET=EQCsdKYwUaXkgJkz2l0ol6qT_WxeRbE_wBCwnEybmR0u5TO8 -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/verifier/3683644a8a52146e981133ba10c050b7b290818a/.env.development -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | VITE_REDIRECT=true -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Build 4 | 5 | on: 6 | pull_request: 7 | branches: ["main"] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Use Node.js ${{ matrix.node-version }} 15 | uses: actions/setup-node@v3 16 | with: 17 | node-version: ${{ matrix.node-version }} 18 | cache: "npm" 19 | - run: npm ci 20 | - run: CI=false npm run build 21 | # - run: npm test 22 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Build and Deploy 4 | 5 | on: 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 🛎️ 16 | uses: actions/checkout@v2.3.1 17 | - name: install dependencies 18 | run: npm ci 19 | - name: Build 20 | run: CI=false npm run build 21 | env: 22 | VITE_APP_GA: ${{ secrets.GOOGLE_ANALYTICS }} 23 | - name: Deploy 🚀 24 | uses: JamesIves/github-pages-deploy-action@4.1.4 25 | with: 26 | branch: gh-pages # The branch the action should deploy to. 27 | folder: dist # The folder the action should deploy. 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .idea 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /dist 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | .env 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | temp/ 28 | 29 | .secret -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx pretty-quick --staged 5 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "jsxBracketSameLine": true, 4 | "printWidth": 100, 5 | "semi": true 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 TON Community 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | 31 | 32 | 33 | 37 | TON Contract Verifier 38 | 45 | 46 | 47 | 48 | 49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ton-contract-verifier", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "homepage": "https://verifier.ton.org", 7 | "scripts": { 8 | "dev": "vite", 9 | "build": "tsc && vite build && cp dist/index.html dist/404.html", 10 | "preview": "vite preview", 11 | "prepare": "husky install", 12 | "analyze": "source-map-explorer 'dist/assets/*.js' --no-border-checks" 13 | }, 14 | "dependencies": { 15 | "@aws-crypto/sha256-js": "^2.0.2", 16 | "@dnd-kit/modifiers": "^6.0.0", 17 | "@dnd-kit/sortable": "^7.0.1", 18 | "@emotion/react": "^11.10.4", 19 | "@emotion/styled": "^11.10.4", 20 | "@mui/icons-material": "^5.10.9", 21 | "@mui/material": "^5.10.10", 22 | "@orbs-network/ton-access": "^2.3.3", 23 | "@tact-lang/compiler": "^1.1.2", 24 | "@tanstack/react-query": "^4.13.0", 25 | "@ton-community/contract-verifier-sdk": "1.4.0", 26 | "@ton-community/func-js": "^0.3.0", 27 | "@tonconnect/ui-react": "^1.0.0-beta.6", 28 | "bigint-buffer": "^1.1.5", 29 | "buffer": "^6.0.3", 30 | "file-saver": "^2.0.5", 31 | "func-js-bin-0.2.0": "npm:@ton-community/func-js-bin@^0.2.0", 32 | "func-js-bin-0.3.0": "npm:@ton-community/func-js-bin@^0.3.0", 33 | "func-js-bin-0.4.0": "npm:@ton-community/func-js-bin@^0.4.0", 34 | "func-js-bin-0.4.1": "npm:@ton-community/func-js-bin@^0.4.1", 35 | "func-js-bin-0.4.2": "npm:@ton-community/func-js-bin@^0.4.2", 36 | "func-js-bin-0.4.3": "npm:@ton-community/func-js-bin@^0.4.3", 37 | "func-js-bin-0.4.4": "npm:@ton-community/func-js-bin@^0.4.4", 38 | "immer": "^9.0.17", 39 | "javascript-time-ago": "^2.5.7", 40 | "jszip": "^3.10.1", 41 | "notistack": "^2.0.8", 42 | "react": "^18.2.0", 43 | "react-device-detect": "^2.2.2", 44 | "react-dom": "^18.2.0", 45 | "react-dropzone": "^14.2.3", 46 | "react-ga4": "^1.4.1", 47 | "react-hook-form": "^7.48.2", 48 | "react-qr-code": "^2.0.8", 49 | "react-router-dom": "^6.4.2", 50 | "source-map-explorer": "^2.5.3", 51 | "ton": "^13.9.0", 52 | "ton-core": "^0.53.0", 53 | "tvm-disassembler": "^2.0.2", 54 | "web-tree-sitter": "^0.20.7", 55 | "zustand": "^4.1.3" 56 | }, 57 | "devDependencies": { 58 | "@types/file-saver": "^2.0.5", 59 | "@types/node": "^18.11.4", 60 | "@types/react": "^18.0.17", 61 | "@types/react-dom": "^18.0.6", 62 | "@vitejs/plugin-react": "^2.1.0", 63 | "husky": "^8.0.0", 64 | "typescript": "^4.6.4", 65 | "vite": "^3.1.0" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /public/tonconnect-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://verifier.ton.org", 3 | "name": "TON Verifier", 4 | "iconUrl": "https://verifier.ton.org/tonverifier.png" 5 | } 6 | -------------------------------------------------------------------------------- /public/tonverifier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/verifier/3683644a8a52146e981133ba10c050b7b290818a/public/tonverifier.png -------------------------------------------------------------------------------- /public/tree-sitter-func.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/verifier/3683644a8a52146e981133ba10c050b7b290818a/public/tree-sitter-func.wasm -------------------------------------------------------------------------------- /public/tree-sitter.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/verifier/3683644a8a52146e981133ba10c050b7b290818a/public/tree-sitter.wasm -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # contract-verifier 2 | 3 | A UI app to display verified contract proofs and to submit Ton smart contract sources for on-chain verification. 4 | 5 | ## Related repositories 6 | 7 | This repo is a part of the following: 8 | 9 | 1. [contract-verifier-contracts](https://github.com/ton-community/contract-verifier-contracts) - Sources registry contracts which stores an on-chain proof per code cell hash. 10 | 2. [contract-verifier-backend](https://github.com/ton-community/contract-verifier-backend) - Backend for compiling FunC and returning a signature over a message containing the resulting code cell hash. 11 | 3. [contract-verifier-sdk](https://github.com/ton-community/contract-verifier-sdk) - A UI component to fetch and display sources from Ton blockchain and IPFS, including FunC code highlighting. 12 | 4. contract-verifier (this repo) - A UI app to interact with the backend, contracts and publish an on-chain proof. 13 | 14 | ## Deployment 15 | 16 | This app is deployed via github actions on github pages for this repository. 17 | 18 | ### Environment variables 19 | 20 | - `VITE_VERIFIER_ID` - id of the verifier registered with the verifier registry 21 | - `VITE_SOURCES_REGISTRY` / `VITE_SOURCES_REGISTRY_TESTNET` - sources registry to fetch data from 22 | - `VITE_BACKEND_URL` / `VITE_BACKEND_URL_TESTNET` - urls for backend (split by comma) 23 | 24 | ## Running 25 | 26 | - `npm install` 27 | - `npm run dev` 28 | 29 | ## Appendix: Adding new FunC versions 30 | 31 | 1. Add the wasm binding in package json, as such: 32 | 33 | ``` 34 | "func-js-bin-0.4.3": "npm:@ton-community/func-js-bin@^0.4.3", 35 | ``` 36 | 37 | 2. Add the version to https://github.com/ton-community/contract-verifier-config 38 | 3. Redeploy backend https://github.com/ton-community/contract-verifier-backend 39 | 40 | ## License 41 | 42 | MIT 43 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Mulish:400,500,600,700,800"); 2 | @import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&display=swap"); 3 | 4 | body { 5 | background-color: #f7f9fb; 6 | margin: 0; 7 | font-family: "Mulish"; 8 | text-rendering: optimizeLegibility; 9 | -webkit-font-smoothing: antialiased; 10 | } 11 | 12 | .App { 13 | max-width: 1160px; 14 | margin: 0 auto; 15 | } 16 | -------------------------------------------------------------------------------- /src/assets/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/compiler.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/assets/contract.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/dnd.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/drag-handle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/assets/file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/github-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/github-footer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/github-hover.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/light-bulb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/assets/like.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/qr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/recent-search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/show.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/sources.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/telegram-hover.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/telegram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/tonhub-qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/verifier/3683644a8a52146e981133ba10c050b7b290818a/src/assets/tonhub-qr.png -------------------------------------------------------------------------------- /src/assets/tonhub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/verifier/3683644a8a52146e981133ba10c050b7b290818a/src/assets/tonhub.png -------------------------------------------------------------------------------- /src/assets/tonkeeper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/verifier/3683644a8a52146e981133ba10c050b7b290818a/src/assets/tonkeeper.png -------------------------------------------------------------------------------- /src/assets/tonverifier.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/verifier/3683644a8a52146e981133ba10c050b7b290818a/src/assets/tonverifier.ico -------------------------------------------------------------------------------- /src/assets/tonverifier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/verifier/3683644a8a52146e981133ba10c050b7b290818a/src/assets/tonverifier.png -------------------------------------------------------------------------------- /src/assets/undo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/upload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/verification-bomb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/verification.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/assets/verified.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/verify_icon_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/AddSourcesBlock.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { CenteringBox, DataBox } from "./Common.styled"; 3 | import { useFileStore } from "../lib/useFileStore"; 4 | import { useSubmitSources } from "../lib/useSubmitSources"; 5 | import { FileUploaderArea } from "./FileUploaderArea"; 6 | import { FileTable } from "./FileTable"; 7 | import CompilerSettings from "./CompilerSettings"; 8 | import { CompileOutput } from "./CompileOutput"; 9 | import { Box, styled } from "@mui/system"; 10 | import { AppButton } from "./AppButton"; 11 | import { SECTIONS, STEPS, usePublishStore } from "../lib/usePublishSteps"; 12 | import { CircularProgress, Fade } from "@mui/material"; 13 | import { useTonAddress } from "@tonconnect/ui-react"; 14 | import ConnectButton from "./ConnectButton"; 15 | 16 | const ContentBox = styled(Box)({ 17 | padding: "15px 24px", 18 | }); 19 | 20 | export function AddSourcesBlock() { 21 | const walletAddress = useTonAddress(); 22 | const { hasFiles } = useFileStore(); 23 | const { step, proceedToPublish, toggleSection, currentSection } = usePublishStore(); 24 | const { mutate, data, error, isLoading, compileStatus } = useSubmitSources(); 25 | 26 | const canPublish = !!data?.result?.msgCell; 27 | 28 | const onSectionExpand = () => toggleSection(SECTIONS.SOURCES); 29 | 30 | return ( 31 | 32 | 35 | 36 | 37 | {currentSection === SECTIONS.SOURCES && ( 38 | 39 | 40 | <> 41 | {hasFiles() && ( 42 | <> 43 | 44 | 45 | 46 | )} 47 | {(data || error) && } 48 | {hasFiles() && ( 49 | 50 | {!walletAddress ? ( 51 | 52 | ) : !data?.result?.msgCell ? ( 53 | { 63 | mutate(null); 64 | }}> 65 | {isLoading && ( 66 | 73 | )} 74 | Compile 75 | 76 | ) : ( 77 | 87 | Ready to publish 88 | 89 | )} 90 | 91 | )} 92 | 93 | 94 | 95 | )} 96 | 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /src/components/AppButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | import { Button, styled } from "@mui/material"; 3 | 4 | interface StyledButtonProps { 5 | fontSize?: number; 6 | fontWeight?: number; 7 | transparent?: boolean; 8 | background?: string; 9 | hoverBackground?: string; 10 | width?: number; 11 | height?: number; 12 | textColor?: string; 13 | } 14 | 15 | const StyledButton = styled(Button)((props: StyledButtonProps) => ({ theme }) => ({ 16 | display: "flex", 17 | alignItems: "center", 18 | justifyContent: "center", 19 | gap: 8, 20 | padding: "0px 16px", 21 | margin: "auto", 22 | maxWidth: 160, 23 | width: props.width || "100%", 24 | height: props.height || "100%", 25 | fontSize: props.fontSize || 14, 26 | fontWeight: props.fontWeight || 400, 27 | boxShadow: "none", 28 | borderRadius: 40, 29 | border: props.transparent ? "1px solid #50A7EA" : "", 30 | background: props.background || "inherit", 31 | whiteSpace: "nowrap", 32 | textTransform: "none", 33 | color: props.textColor || "#000", 34 | "&:hover": { 35 | background: props.hoverBackground || "inherit", 36 | }, 37 | "& img": { 38 | maxWidth: 22, 39 | }, 40 | "&:disabled": { 41 | background: "#D9D9D9", 42 | }, 43 | [theme.breakpoints.down(900)]: { 44 | padding: 0, 45 | minWidth: 25, 46 | }, 47 | })); 48 | 49 | interface AppButtonProps extends StyledButtonProps { 50 | children: ReactNode; 51 | loading?: boolean; 52 | disabled?: boolean; 53 | onClick?: () => void; 54 | type?: "button" | "submit" | "reset"; 55 | } 56 | 57 | export const AppButton: React.FC = ({ 58 | children, 59 | disabled, 60 | onClick, 61 | type = "button", 62 | fontSize = 14, 63 | fontWeight, 64 | transparent, 65 | background, 66 | hoverBackground, 67 | width, 68 | height, 69 | textColor, 70 | }) => { 71 | return ( 72 | {}} 84 | variant={transparent ? "outlined" : "contained"} 85 | disabled={disabled} 86 | disableElevation> 87 | {children} 88 | 89 | ); 90 | }; 91 | -------------------------------------------------------------------------------- /src/components/AppNotification.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Box, styled } from "@mui/system"; 3 | 4 | export enum NotificationType { 5 | ERROR = "Error", 6 | HINT = "Hint", 7 | SUCCESS = "Success", 8 | INFO = "Info", 9 | } 10 | 11 | interface NotificationBoxProps { 12 | borderColor?: string; 13 | backgroundColor?: string; 14 | singleLine?: boolean; 15 | noBottomMargin?: boolean; 16 | noTopMargin?: boolean; 17 | } 18 | 19 | const NotificationBox = styled(Box)((props: NotificationBoxProps) => ({ 20 | padding: `${props.singleLine ? 0 : 15}px 25px`, 21 | marginBottom: props.noBottomMargin ? 0 : 24, 22 | marginTop: props.noTopMargin ? 0 : 24, 23 | background: props.backgroundColor || "", 24 | border: `1px solid ${props.borderColor || "#D8D8D8"}`, 25 | borderRadius: 12, 26 | })); 27 | 28 | interface CompilationNotificationProps { 29 | type: NotificationType; 30 | title: React.ReactNode; 31 | notificationBody: React.ReactNode; 32 | singleLine?: boolean; 33 | noBottomMargin?: boolean; 34 | noTopMargin?: boolean; 35 | noMargin?: boolean; 36 | } 37 | 38 | export function AppNotification({ 39 | title, 40 | type, 41 | notificationBody, 42 | singleLine, 43 | noBottomMargin, 44 | noTopMargin, 45 | }: CompilationNotificationProps) { 46 | let borderColor; 47 | let backgroundColor; 48 | 49 | switch (type) { 50 | case NotificationType.INFO: 51 | backgroundColor = "rgba(216, 216, 216, 0.2);"; 52 | break; 53 | case NotificationType.ERROR: 54 | borderColor = "rgba(252, 86, 86, 0.42);"; 55 | backgroundColor = "rgba(252, 86, 86, 0.08);"; 56 | break; 57 | case NotificationType.HINT: 58 | backgroundColor = "rgba(94, 117, 232, 0.1);"; 59 | break; 60 | case NotificationType.SUCCESS: 61 | backgroundColor = "#D6FFCE"; 62 | break; 63 | } 64 | 65 | return ( 66 | 72 | {title} 73 | {notificationBody} 74 | 75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /src/components/AppPopup.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | import CloseRoundedIcon from "@mui/icons-material/CloseRounded"; 3 | import { Box, styled } from "@mui/system"; 4 | import { Dialog, IconButton } from "@mui/material"; 5 | 6 | const StyledClose = styled(IconButton)(() => ({ 7 | color: "#000", 8 | })); 9 | 10 | const StyledChildren = styled(Box)({ 11 | display: "flex", 12 | alignItems: "center", 13 | flexDirection: "column", 14 | "& .title": { 15 | texAlign: "center", 16 | fontWeight: 500, 17 | fontSize: 20, 18 | marginBottom: 20, 19 | }, 20 | "& .base-button": { 21 | height: 40, 22 | marginTop: 30, 23 | }, 24 | }); 25 | 26 | export interface AppPopupProps { 27 | open: boolean; 28 | onClose?: () => void; 29 | children: ReactNode; 30 | backgroundColor?: string; 31 | blur?: boolean; 32 | className?: string; 33 | maxWidth: number | string; 34 | hideCloseButton?: boolean; 35 | paddingTop?: boolean; 36 | } 37 | 38 | export function AppPopup({ 39 | open, 40 | onClose, 41 | children, 42 | backgroundColor = "rgba(48, 48, 48, 0.4)", 43 | blur = true, 44 | className = "", 45 | maxWidth, 46 | hideCloseButton, 47 | paddingTop, 48 | }: AppPopupProps) { 49 | return ( 50 | 70 | 77 | {!hideCloseButton && ( 78 | 79 | 80 | 81 | 82 | 83 | )} 84 | 85 | {children} 86 | 87 | 88 | 89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /src/components/Button.css: -------------------------------------------------------------------------------- 1 | /*TODO delete*/ 2 | .Button { 3 | font-weight: 700; 4 | background-color: #0088cc; 5 | border-radius: 8px; 6 | padding: 10px 20px; 7 | cursor: pointer; 8 | display: inline-block; 9 | } 10 | 11 | .Button-disabled { 12 | background-color: #cccccc; 13 | cursor: not-allowed; 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | //TODO replace with AppButton and delete 2 | import "./Button.css"; 3 | import { Button as MUIButton, ButtonProps } from "@mui/material"; 4 | import { styled } from "@mui/material/styles"; 5 | 6 | const StyledButton = styled(MUIButton)({ 7 | borderRadius: 40, 8 | fontFamily: "inherit", 9 | fontWeight: 700, 10 | textTransform: "none", 11 | background: "#0088CC", 12 | "&:disabled": { 13 | backgroundColor: "#e0e0e0", 14 | }, 15 | }); 16 | 17 | function Button( 18 | props: { 19 | text: string; 20 | } & ButtonProps, 21 | ) { 22 | return ( 23 | 32 | {props.text} 33 | 34 | ); 35 | } 36 | 37 | export default Button; 38 | -------------------------------------------------------------------------------- /src/components/Common.styled.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from "@mui/material/styles"; 2 | import { Box, Typography } from "@mui/material"; 3 | 4 | const CenteringBox = styled(Box)(() => ({ 5 | display: "flex", 6 | alignItems: "center", 7 | })); 8 | 9 | const DataBox = styled(Box)({ 10 | boxSizing: "border-box", 11 | maxWidth: 1160, 12 | width: "100%", 13 | marginTop: 20, 14 | backgroundColor: "#fff", 15 | borderRadius: 20, 16 | color: "#000", 17 | border: "0.5px solid rgba(114, 138, 150, 0.24)", 18 | boxShadow: "rgb(114 138 150 / 8%) 0px 2px 16px", 19 | }); 20 | 21 | const TitleBox = styled(CenteringBox)({ 22 | padding: "30px 24px 0 24px", 23 | }); 24 | 25 | const IconBox = styled(CenteringBox)({ 26 | marginRight: 8, 27 | }); 28 | 29 | const TitleText = styled(Typography)({ 30 | fontSize: 20, 31 | color: "#161C28", 32 | fontWeight: 800, 33 | }); 34 | 35 | export { CenteringBox, DataBox, TitleBox, TitleText, IconBox }; 36 | -------------------------------------------------------------------------------- /src/components/CompilerBlock.tsx: -------------------------------------------------------------------------------- 1 | import compilerIcon from "../assets/compiler.svg"; 2 | import { DataBlock, DataRowItem } from "./DataBlock"; 3 | import { useLoadContractProof } from "../lib/useLoadContractProof"; 4 | 5 | import TimeAgo from "javascript-time-ago"; 6 | import en from "javascript-time-ago/locale/en"; 7 | import { funcVersionToLink, fiftVersionToLink, tactVersionToLink, tolkVersionToLink, dropPatchVersionZero } from "../utils/linkUtils"; 8 | import { 9 | FiftCliCompileSettings, 10 | FuncCompilerSettings, 11 | TactCliCompileSettings, 12 | TolkCliCompileSettings, 13 | } from "@ton-community/contract-verifier-sdk"; 14 | 15 | TimeAgo.addDefaultLocale(en); 16 | 17 | export function CompilerBlock() { 18 | const { data } = useLoadContractProof(); 19 | 20 | const compilerSettings = data!.compilerSettings; 21 | 22 | const dataRows: DataRowItem[] = []; 23 | 24 | if (data) { 25 | dataRows.push({ 26 | title: "Compiler", 27 | value: `${data!.compiler!}`, 28 | }); 29 | 30 | if (data.compiler === "func") { 31 | const funcVersion = (compilerSettings as FuncCompilerSettings)?.funcVersion; 32 | dataRows.push({ 33 | title: "Version", 34 | value: funcVersion, 35 | color: "#0088CC", 36 | customLink: funcVersion && funcVersionToLink(funcVersion), 37 | }); 38 | } else if (data.compiler === "fift") { 39 | const fiftVersion = (compilerSettings as FiftCliCompileSettings)?.fiftVersion; 40 | dataRows.push({ 41 | title: "Version", 42 | value: fiftVersion, 43 | color: "#0088CC", 44 | customLink: fiftVersionToLink(fiftVersion), 45 | }); 46 | } else if (data.compiler === "tact") { 47 | const tactVersion = (compilerSettings as TactCliCompileSettings)?.tactVersion; 48 | dataRows.push({ 49 | title: "Version", 50 | value: tactVersion, 51 | color: "#0088CC", 52 | customLink: tactVersionToLink(tactVersion), 53 | }); 54 | } else if (data.compiler === "tolk") { 55 | const tolkVersion = (compilerSettings as TolkCliCompileSettings)?.tolkVersion; 56 | dataRows.push({ 57 | title: "Version", 58 | value: dropPatchVersionZero(tolkVersion), 59 | color: "#0088CC", 60 | customLink: tolkVersionToLink(tolkVersion), 61 | }); 62 | } 63 | if (data.compiler == "func") { 64 | dataRows.push({ 65 | title: "Command", 66 | // @ts-ignore 67 | value: compilerSettings?.commandLine, 68 | showIcon: true, 69 | tooltip: true, 70 | }); 71 | } 72 | dataRows.push({ 73 | title: "Verified on", 74 | value: data.verificationDate?.toLocaleDateString() ?? "", 75 | }); 76 | } 77 | 78 | return ; 79 | } 80 | -------------------------------------------------------------------------------- /src/components/CompilerSetting.styled.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from "@mui/system"; 2 | import { FormControl, FormLabel, Select } from "@mui/material"; 3 | 4 | const CompilerFormControl = styled(FormControl)({ 5 | flexGrow: 1, 6 | }); 7 | 8 | const CompilerSelect = styled(Select)(({ theme }) => ({ 9 | borderRadius: theme.spacing(1.2), 10 | height: theme.spacing(5.3), 11 | minWidth: 150, 12 | ".MuiOutlinedInput-notchedOutline": { 13 | border: "1px solid #D8D8D8", 14 | }, 15 | "&.Mui-focused .MuiOutlinedInput-notchedOutline": { 16 | border: "1px solid #807e7e", 17 | }, 18 | "&:hover .MuiOutlinedInput-notchedOutline": { 19 | border: "1px solid #b0b0b0", 20 | }, 21 | })); 22 | 23 | const CompilerLabel = styled(FormLabel)(({ theme }) => ({ 24 | color: "#000", 25 | fontSize: 12, 26 | marginLeft: theme.spacing(1), 27 | marginBottom: theme.spacing(1), 28 | })); 29 | 30 | const DirectoryInput = styled("input")(({ theme }) => ({ 31 | display: "flex", 32 | alignItems: "center", 33 | boxSizing: "border-box", 34 | width: "100%", 35 | flex: 2, 36 | height: theme.spacing(5.3), 37 | borderRadius: theme.spacing(1.2), 38 | border: "1px solid #D8D8D8", 39 | outline: "none", 40 | padding: "0 40px 0 125px", 41 | color: "#000", 42 | background: "transparent", 43 | fontFamily: "Mulish", 44 | fontSize: 14, 45 | "&:hover": { 46 | border: "1px solid #b0b0b0", 47 | }, 48 | "&:focus": { 49 | border: "1px solid #807e7e", 50 | }, 51 | })); 52 | 53 | export { DirectoryInput, CompilerLabel, CompilerSelect, CompilerFormControl }; 54 | -------------------------------------------------------------------------------- /src/components/ConnectButton.tsx: -------------------------------------------------------------------------------- 1 | import { useTonConnectUI } from "@tonconnect/ui-react"; 2 | import React from "react"; 3 | import { AppButton } from "./AppButton"; 4 | 5 | function ConnectButton() { 6 | const [tonConnect] = useTonConnectUI(); 7 | return ( 8 | tonConnect.connectWallet()}> 17 | Connect wallet 18 | 19 | ); 20 | } 21 | 22 | export default ConnectButton; 23 | -------------------------------------------------------------------------------- /src/components/ConnectorHeader.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Box, IconButton, styled, Typography, useTheme } from "@mui/material"; 3 | import CloseRoundedIcon from "@mui/icons-material/CloseRounded"; 4 | 5 | const StyledContainer = styled(Box)({ 6 | display: "flex", 7 | alignItems: "center", 8 | justifyContent: "space-between", 9 | position: "relative", 10 | paddingBottom: 10, 11 | marginBottom: 20, 12 | width: "100%", 13 | "& h4": { 14 | fontSize: 18, 15 | fontWeight: 400, 16 | }, 17 | }); 18 | 19 | const StyledSeparator = styled(Box)(({ theme }) => ({ 20 | position: "absolute", 21 | left: 0, 22 | bottom: 0, 23 | width: "100%", 24 | height: 1, 25 | background: theme.palette.text.primary, 26 | opacity: 0.2, 27 | })); 28 | 29 | interface ConnectorHeaderProps { 30 | title: string; 31 | onClose?: () => void; 32 | } 33 | 34 | export function ConnectorHeader({ title, onClose }: ConnectorHeaderProps) { 35 | const theme = useTheme(); 36 | 37 | return ( 38 | 39 | {title} 40 | {onClose && ( 41 | 42 | 43 | 44 | )} 45 | 46 | 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /src/components/ContractBlock.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useContractAddress } from "../lib/useContractAddress"; 3 | import { useLoadContractInfo } from "../lib/useLoadContractInfo"; 4 | import contractIcon from "../assets/contract.svg"; 5 | import { DataBlock, DataRowItem } from "./DataBlock"; 6 | import { useLoadContractProof } from "../lib/useLoadContractProof"; 7 | import { workchainForAddress } from "../lib/workchainForAddress"; 8 | import { formatBalance } from "../utils/numberUtils"; 9 | import { useEffect } from "react"; 10 | 11 | function useToggle(valA: T, valB: T): [T, () => void] { 12 | const [state, setState] = useState(valA); 13 | 14 | useEffect(() => { 15 | setState(valA); 16 | }, [valA, valB]); 17 | 18 | return [ 19 | state, 20 | () => { 21 | setState(state === valA ? valB : valA); 22 | }, 23 | ]; 24 | } 25 | 26 | export function ContractBlock() { 27 | const { contractAddress, contractAddressHex } = useContractAddress(); 28 | const { data, isLoading } = useLoadContractInfo(); 29 | const { data: proofData } = useLoadContractProof(); 30 | const dataRows: DataRowItem[] = []; 31 | 32 | const [displayAddress, toggleDisplayAddress] = useToggle(contractAddress, contractAddressHex); 33 | const [displayCodeCellHash, toggleDisplayCodeCellHash] = useToggle( 34 | data?.codeCellHash.base64, 35 | data?.codeCellHash.hex, 36 | ); 37 | const [displayDataCellHash, toggleDisplayDataCellHash] = useToggle( 38 | data?.dataCellHash.base64, 39 | data?.dataCellHash.hex, 40 | ); 41 | 42 | const [displayLibraryHash, toggleDisplayLibraryHash] = useToggle( 43 | data?.libraryHash.base64, 44 | data?.libraryHash.hex, 45 | ); 46 | 47 | if (data) { 48 | dataRows.push({ 49 | title: "Address", 50 | value: displayAddress ?? "", 51 | showIcon: true, 52 | onClick: () => { 53 | toggleDisplayAddress(); 54 | }, 55 | tooltip: true, 56 | subtitle: workchainForAddress(contractAddress || ""), 57 | }); 58 | dataRows.push({ 59 | title: "Balance", 60 | value: `${formatBalance.format(parseFloat(data.balance))} TON`, 61 | }); 62 | dataRows.push({ 63 | title: "Code Hash", 64 | value: displayCodeCellHash ?? "", 65 | showIcon: true, 66 | onClick: () => { 67 | toggleDisplayCodeCellHash(); 68 | }, 69 | tooltip: true, 70 | }); 71 | dataRows.push({ 72 | title: "Data Hash", 73 | value: displayDataCellHash ?? "", 74 | showIcon: true, 75 | onClick: () => { 76 | toggleDisplayDataCellHash(); 77 | }, 78 | tooltip: true, 79 | }); 80 | 81 | if (data?.libraryHash.base64) { 82 | dataRows.push({ 83 | title: "Library Code Cell Hash", 84 | value: displayLibraryHash ?? "", 85 | showIcon: true, 86 | onClick: () => { 87 | toggleDisplayLibraryHash(); 88 | }, 89 | tooltip: true, 90 | }); 91 | dataRows.push({ 92 | title: "", 93 | value: "", 94 | showIcon: true, 95 | }); 96 | } 97 | } 98 | 99 | return ( 100 | 107 | ); 108 | } 109 | -------------------------------------------------------------------------------- /src/components/ContractSourceCode.css: -------------------------------------------------------------------------------- 1 | #myVerifierFiles { 2 | border-right: 1px solid #e8e8e8; 3 | width: 240px; 4 | font-weight: 500; 5 | font-size: 14px; 6 | } 7 | 8 | #myVerifierContent code { 9 | background: #fff; 10 | font-size: 14px; 11 | max-width: 100%; 12 | line-height: 25px; 13 | padding-left: 20px; 14 | height: 800px; 15 | -webkit-text-size-adjust: 100%; 16 | } 17 | 18 | #myVerifierFiles .contract-verifier-file.active { 19 | background: #f7f9fb; 20 | } 21 | 22 | #myVerifierFiles .contract-verifier-tree-item { 23 | padding: 13px 6px; 24 | } 25 | 26 | @media screen and (max-width: 1200px) { 27 | #myVerifierFiles { 28 | width: 190px; 29 | } 30 | } 31 | 32 | @media screen and (max-width: 1100px) { 33 | #myVerifierFiles { 34 | width: 170px; 35 | } 36 | } 37 | 38 | @media screen and (max-width: 1000px) { 39 | #myVerifierFiles { 40 | width: 150px; 41 | } 42 | } 43 | 44 | @media screen and (max-width: 900px) { 45 | #myVerifierContainer { 46 | flex-direction: column; 47 | } 48 | 49 | #myVerifierContent code { 50 | padding-left: 0; 51 | } 52 | 53 | #myVerifierFiles { 54 | max-height: 400px; 55 | height: content-box; 56 | overflow-y: scroll; 57 | width: 100%; 58 | border-right: none; 59 | border-bottom: 1px solid #e8e8e8; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/components/DataBlock.styled.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from "@mui/system"; 2 | import { CenteringBox, DataBox } from "./Common.styled"; 3 | import { Box, Typography } from "@mui/material"; 4 | 5 | const DataFlexibleBox = styled(DataBox)({ 6 | minWidth: 100, 7 | }); 8 | 9 | interface DataRowProps { 10 | isShrinked?: boolean; 11 | isExtraSmallScreen?: boolean; 12 | } 13 | 14 | const DataRowsBox = styled(Box)((props: DataRowProps) => ({ 15 | display: props.isShrinked && !props.isExtraSmallScreen ? "flex" : "inherit", 16 | flexWrap: props.isShrinked && !props.isExtraSmallScreen ? "wrap" : "inherit", 17 | columnGap: props.isShrinked && !props.isExtraSmallScreen ? 30 : "", 18 | padding: props.isShrinked && !props.isExtraSmallScreen ? "0 30px" : "", 19 | "&>*:last-child": { 20 | borderBottom: props.isShrinked ? "" : "none !important", 21 | }, 22 | "&:last-child": { 23 | marginBottom: 3, 24 | }, 25 | })); 26 | 27 | const DataRow = styled(CenteringBox)((props: DataRowProps) => ({ 28 | boxSizing: props.isShrinked ? "border-box" : "inherit", 29 | flex: props.isShrinked ? "40%" : "inherit", 30 | width: 31 | props.isShrinked && !props.isExtraSmallScreen 32 | ? 0 33 | : !props.isShrinked 34 | ? "" 35 | : props.isExtraSmallScreen 36 | ? "100%" 37 | : "", 38 | minHeight: 38, 39 | padding: "10px 24px", 40 | transition: "background .15s", 41 | borderTop: "1px solid rgba(114, 138, 150, 0.2)", 42 | })); 43 | 44 | const DataRowTitle = styled(Typography)({ 45 | fontSize: 14, 46 | color: "#000", 47 | minWidth: 90, 48 | fontWeight: 800, 49 | }); 50 | 51 | const DataRowValue = styled(Typography)({ 52 | width: "100%", 53 | wordBreak: "break-word", 54 | fontSize: 14, 55 | color: "#728A96", 56 | }); 57 | 58 | const IconsWrapper = styled(CenteringBox)({ 59 | minWidth: 25, 60 | justifyContent: "flex-end", 61 | }); 62 | 63 | export { DataRow, DataRowValue, DataRowTitle, DataRowsBox, DataFlexibleBox, IconsWrapper }; 64 | -------------------------------------------------------------------------------- /src/components/DataBlock.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from "react"; 2 | import { IconButton, Link, useMediaQuery, Tooltip, Box } from "@mui/material"; 3 | import { DataBox, IconBox, TitleBox, TitleText } from "./Common.styled"; 4 | import copy from "../assets/copy.svg"; 5 | import useNotification from "../lib/useNotification"; 6 | import { 7 | DataFlexibleBox, 8 | DataRow, 9 | DataRowsBox, 10 | DataRowTitle, 11 | DataRowValue, 12 | IconsWrapper, 13 | } from "./DataBlock.styled"; 14 | 15 | export interface DataRowItem { 16 | title: string; 17 | value?: string; 18 | showIcon?: boolean; 19 | color?: string; 20 | customLink?: string; 21 | tooltip?: boolean; 22 | onClick?: () => void; 23 | subtitle?: string; 24 | } 25 | 26 | interface DataBlockProps { 27 | isFlexibleWrapper?: boolean; 28 | title: React.ReactNode; 29 | icon: string; 30 | dataRows: DataRowItem[]; 31 | isLoading?: boolean; 32 | } 33 | 34 | const renderRowValue = ( 35 | value?: string, 36 | customLink?: string, 37 | withTooltip?: boolean, 38 | subtitle?: string, 39 | ) => { 40 | const WrappingLink = ({ children }: { children: any }) => 41 | customLink && !!value ? ( 42 | 49 | {children} 50 | 51 | ) : ( 52 | <>{children} 53 | ); 54 | 55 | const WrappingTooltip = ({ children }: { children: any }) => 56 | withTooltip ? ( 57 | 58 | {children} 59 | 60 | ) : ( 61 | <>{children} 62 | ); 63 | 64 | return ( 65 | 66 | {value ?? "-"} 67 | {subtitle ?? ""} 68 | 69 | ); 70 | }; 71 | 72 | export function DataBlock({ isFlexibleWrapper, icon, title, dataRows, isLoading }: DataBlockProps) { 73 | const Wrapper = isFlexibleWrapper ? DataFlexibleBox : DataBox; 74 | const { showNotification } = useNotification(); 75 | const isExtraSmallScreen = useMediaQuery("(max-width: 500px)"); 76 | 77 | const onCopy = useCallback(async (value: string) => { 78 | navigator.clipboard.writeText(value); 79 | showNotification("Copied to clipboard!", "success"); 80 | }, []); 81 | 82 | return ( 83 | 84 | 85 | 86 | Block icon 87 | 88 | {title} 89 | 90 | 91 | {dataRows.map( 92 | ({ title, value, showIcon, color, customLink, tooltip, onClick, subtitle }) => { 93 | return ( 94 | 98 | {title} 99 | 100 | {renderRowValue(value, customLink, tooltip, subtitle)} 101 | 102 | {showIcon && ( 103 | 104 | {value && ( 105 | onCopy(value)}> 106 | Copy icon 107 | 108 | )} 109 | 110 | )} 111 | 112 | ); 113 | }, 114 | )} 115 | 116 | 117 | ); 118 | } 119 | -------------------------------------------------------------------------------- /src/components/DisassembledSourceCode.tsx: -------------------------------------------------------------------------------- 1 | import { useLoadContractInfo } from "../lib/useLoadContractInfo"; 2 | import hljs from "highlight.js/lib/core"; 3 | // @ts-ignore 4 | import hljsDefine from "highlightjs-func"; 5 | import React, { useEffect, useRef } from "react"; 6 | import "highlight.js/styles/atom-one-light.css"; 7 | import { useMediaQuery, useTheme } from "@mui/material"; 8 | 9 | hljsDefine(hljs); 10 | 11 | interface DisassembledSourceCodeProps { 12 | button: React.ReactNode; 13 | } 14 | 15 | export function DisassembledSourceCode({ button }: DisassembledSourceCodeProps) { 16 | const { data: contractInfo } = useLoadContractInfo(); 17 | const theme = useTheme(); 18 | const headerSpacings = useMediaQuery(theme.breakpoints.down("lg")); 19 | const ref = useRef(null); 20 | 21 | useEffect(() => { 22 | hljs.highlightElement(ref.current); 23 | }, [contractInfo?.decompiled, ref.current]); 24 | 25 | return ( 26 |
36 |       
37 |         
43 | {contractInfo?.decompiled 44 | ?.trim() 45 | .split("\n") 46 | .map((_, i) => i + 1) 47 | .join("\n")} 48 |
49 |
50 | {contractInfo?.decompiled} 51 |
52 |
53 | {button} 54 |
55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /src/components/FileTable.styled.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from "@mui/system"; 2 | import TableCell from "@mui/material/TableCell"; 3 | 4 | const DirectoryBox = styled("input")({ 5 | display: "flex", 6 | alignItems: "center", 7 | width: 300, 8 | height: 44, 9 | background: "#FFFFFF", 10 | border: "1px solid #D8D8D8", 11 | borderRadius: "12px", 12 | fontSize: 14, 13 | fontFamily: "Mulish", 14 | paddingLeft: 10, 15 | outline: "none", 16 | "&:hover": { 17 | border: "1px solid #b0b0b0", 18 | }, 19 | "&:focus": { 20 | border: "1px solid #807e7e", 21 | }, 22 | }); 23 | 24 | const BorderLessCell = styled(TableCell)({ 25 | border: "none", 26 | padding: 0, 27 | }); 28 | 29 | const HeaderCell = styled(TableCell)({ 30 | fontWeight: 700, 31 | }); 32 | 33 | const HR = styled("hr")({ 34 | display: "block", 35 | width: "100%", 36 | height: 1, 37 | backgroundColor: "#ccc", 38 | border: "none", 39 | }); 40 | 41 | export { DirectoryBox, HR, HeaderCell, BorderLessCell }; 42 | -------------------------------------------------------------------------------- /src/components/FileUploaderArea.tsx: -------------------------------------------------------------------------------- 1 | import { useFileStore, acceptedFileExtensions } from "../lib/useFileStore"; 2 | import { useDropzone } from "react-dropzone"; 3 | import React from "react"; 4 | import { Box, useMediaQuery } from "@mui/material"; 5 | import { CenteringBox, IconBox, TitleBox, TitleText } from "./Common.styled"; 6 | import sourcesDefault from "../assets/sources.svg"; 7 | import sourcesVerified from "../assets/verified-bold.svg"; 8 | import { AppButton } from "./AppButton"; 9 | import upload from "../assets/upload.svg"; 10 | import { styled } from "@mui/system"; 11 | import { STEPS, usePublishStore } from "../lib/usePublishSteps"; 12 | import { useAddressInput } from "../lib/useAddressInput"; 13 | 14 | const FilesDropzone = styled(CenteringBox)({ 15 | justifyContent: "center", 16 | backgroundColor: "#F7F9FB", 17 | textAlign: "center", 18 | height: 148, 19 | overflow: "hidden", 20 | border: "1px dashed #E3E8EA", 21 | color: "#728A96", 22 | borderRadius: 20, 23 | lineHeight: 148, 24 | cursor: "pointer", 25 | "&:hover": { 26 | border: "1px dashed #9da3a5", 27 | }, 28 | }); 29 | 30 | export function FileUploaderArea() { 31 | const { addFiles, hasFiles } = useFileStore(); 32 | const { step } = usePublishStore(); 33 | const isExtraSmallScreen = useMediaQuery("(max-width: 450px)"); 34 | const { active } = useAddressInput(); 35 | 36 | const onDrop = (acceptedFiles: any) => { 37 | addFiles(acceptedFiles); 38 | }; 39 | 40 | const { getRootProps, getInputProps, isDragActive } = useDropzone({ 41 | onDrop, 42 | accept: { "text/plain": acceptedFileExtensions.map((ext) => `.${ext}`) }, 43 | // noClick: true, 44 | }); 45 | 46 | return ( 47 | <> 48 | 49 | 55 | 56 | 57 | Block icon 63 | 64 | Add sources 65 | 66 | {hasFiles() && step !== STEPS.PUBLISH && ( 67 |
68 | 75 | Sources icon 76 | Upload source 77 | 78 |
79 | )} 80 |
81 |
82 | 83 | 89 | {!hasFiles() && ( 90 | 91 | Drop sources ({acceptedFileExtensions.map((ext) => `.${ext}`).join(", ")}) here 92 | 93 | )} 94 | 95 | 96 | { 100 | // @ts-ignore 101 | e.target.value = ""; 102 | }} 103 | style={{ display: "none" }} 104 | id="fileUpload" 105 | type="file" 106 | multiple 107 | accept={acceptedFileExtensions.join(",")} 108 | // ref={inputRef} 109 | // @ts-ignore 110 | /> 111 | 112 | 113 | ); 114 | } 115 | -------------------------------------------------------------------------------- /src/components/Footer.styled.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Link, styled } from "@mui/material"; 2 | 3 | const FooterWrapper = styled(Box)({ 4 | maxWidth: 1160, 5 | width: "calc(100% - 50px)", 6 | margin: "auto", 7 | }); 8 | 9 | const SocialsWrapper = styled(Box)({ 10 | display: "flex", 11 | justifyContent: "space-between", 12 | height: 73, 13 | }); 14 | 15 | const SocialsContent = styled(Box)(({ theme }) => ({ 16 | display: "flex", 17 | alignItems: "center", 18 | justifyContent: "space-between", 19 | gap: theme.spacing(1.5), 20 | })); 21 | 22 | const CredentialsWrapper = styled(Box)(({ theme }) => ({ 23 | display: "flex", 24 | flexWrap: "wrap", 25 | justifyContent: "space-between", 26 | marginTop: theme.spacing(3), 27 | marginBottom: theme.spacing(2), 28 | color: "#728A96", 29 | fontSize: 14, 30 | [theme.breakpoints.down("md")]: { 31 | "& > *": { 32 | marginBottom: `${theme.spacing(1)} !important`, 33 | }, 34 | }, 35 | })); 36 | 37 | const Separator = styled("hr")({ 38 | height: "1px", 39 | backgroundColor: "#e6e6e6", 40 | border: "none", 41 | }); 42 | 43 | const FooterLink = styled(Link)(({ theme }) => ({ 44 | display: "inline-flex", 45 | alignItems: "center", 46 | color: "inherit", 47 | textDecoration: "none", 48 | [theme.breakpoints.down("md")]: { 49 | justifyContent: "flex-end", 50 | }, 51 | })); 52 | 53 | const CenteringWrapper = styled(Box)({ 54 | display: "flex", 55 | justifyContent: "center", 56 | alignItems: "center", 57 | }); 58 | 59 | const ContributedWrapper = styled(CenteringWrapper)(({ theme }) => ({ 60 | [theme.breakpoints.down("md")]: { 61 | minWidth: "100%", 62 | flex: 2, 63 | order: 3, 64 | }, 65 | })); 66 | 67 | const FooterTextBox = styled(CenteringWrapper)(({ theme }) => ({ 68 | [theme.breakpoints.down("md")]: { 69 | minWidth: "50%", 70 | }, 71 | })); 72 | 73 | const FooterTextBoxLeft = styled(FooterTextBox)(({ theme }) => ({ 74 | [theme.breakpoints.down("md")]: { 75 | justifyContent: "start", 76 | }, 77 | [theme.breakpoints.down("sm")]: { 78 | minWidth: 100, 79 | }, 80 | })); 81 | 82 | const FooterTextBoxRight = styled(FooterTextBox)(({ theme }) => ({ 83 | [theme.breakpoints.down("md")]: { 84 | justifyContent: "end", 85 | }, 86 | [theme.breakpoints.down("sm")]: { 87 | minWidth: 100, 88 | }, 89 | })); 90 | 91 | export { 92 | FooterLink, 93 | ContributedWrapper, 94 | FooterTextBox, 95 | FooterTextBoxLeft, 96 | FooterTextBoxRight, 97 | CenteringWrapper, 98 | CredentialsWrapper, 99 | SocialsWrapper, 100 | SocialsContent, 101 | FooterWrapper, 102 | Separator, 103 | }; 104 | -------------------------------------------------------------------------------- /src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | ContributedWrapper, 4 | CredentialsWrapper, 5 | FooterLink, 6 | FooterTextBoxLeft, 7 | FooterTextBoxRight, 8 | FooterWrapper, 9 | Separator, 10 | SocialsContent, 11 | SocialsWrapper, 12 | } from "./Footer.styled"; 13 | import { AppLogo, LinkWrapper } from "./TopBar.styled"; 14 | import { Typography, useMediaQuery } from "@mui/material"; 15 | import heart from "../assets/heart.svg"; 16 | import orbsLogo from "../assets/orbs.svg"; 17 | import telegram from "../assets/telegram.svg"; 18 | import telegramHovered from "../assets/telegram-hover.svg"; 19 | import github from "../assets/github-footer.svg"; 20 | import githubHovered from "../assets/github-hover.svg"; 21 | import { HoverableIcon } from "./HoverableIcon"; 22 | import icon from "../assets/icon.svg"; 23 | import { CenteringBox } from "./Common.styled"; 24 | import { useNavigatePreserveQuery } from "../lib/useNavigatePreserveQuery"; 25 | import { useSwitchNetwork } from "./TestnetBar"; 26 | 27 | export const TELEGRAM_SUPPORT_LINK = "https://t.me/tonverifier"; 28 | 29 | export function Footer() { 30 | const isExtraSmallScreen = useMediaQuery("(max-width: 450px)"); 31 | const navigate = useNavigatePreserveQuery(); 32 | const switchNetwork = useSwitchNetwork(); 33 | 34 | return ( 35 | 36 | 42 | 43 | navigate("/")}> 44 | App icon 45 | TON VERIFIER 46 | 47 | 48 | 49 | 54 | 59 | 60 | 61 | 62 | 63 | 64 | © 2023 65 | 66 | 67 | 68 | Contributed with 69 | 70 | Heart 71 | 72 | by 73 | 74 | Orbs logo 75 | 76 | 80 | Orbs 81 | 82 | 83 | 84 | 85 | 86 | Support 87 | 88 | {!window.isTestnet && ( 89 | { 91 | switchNetwork(); 92 | }} 93 | sx={{ ml: 2, cursor: "pointer" }} 94 | variant="body2"> 95 | Switch to Testnet 96 | 97 | )} 98 | 99 | 100 | 101 | ); 102 | } 103 | -------------------------------------------------------------------------------- /src/components/Getters.styled.tsx: -------------------------------------------------------------------------------- 1 | import { Box, styled, TableCell, Typography } from "@mui/material"; 2 | 3 | export const TitleText = styled(Typography)({ 4 | fontSize: 14, 5 | fontWeight: 700, 6 | }); 7 | 8 | export const TitleSubtext = styled(Typography)({ 9 | fontSize: 12, 10 | color: "#949597", 11 | }); 12 | 13 | export const GetterBox = styled(Box)({ 14 | border: "1px solid #D9D9D988", 15 | overflow: "hidden", 16 | borderRadius: 14, 17 | width: "100%", 18 | paddingBottom: 6, 19 | }); 20 | 21 | export const FlexBoxColumn = styled(Box)({ 22 | display: "flex", 23 | flexDirection: "column", 24 | }); 25 | 26 | export const FlexBoxRow = styled(Box)({ 27 | display: "flex", 28 | flexDirection: "row", 29 | alignItems: "center", 30 | }); 31 | 32 | export const TitleBox = styled(FlexBoxRow)({ 33 | background: "#F7F9FB", 34 | padding: "14px 20px", 35 | }); 36 | 37 | export const ContentBox = styled(FlexBoxColumn)({ 38 | fontSize: 14, 39 | }); 40 | 41 | export const TableCellStyled = styled(TableCell)({ 42 | padding: "10px 6px", 43 | borderBottom: 0, 44 | }); 45 | 46 | export const TypeChip = styled(Box)({ 47 | border: "1px solid #D8D8D8", 48 | background: "white", 49 | borderRadius: 6, 50 | padding: "0px 10px", 51 | fontSize: 12, 52 | textAlign: "center", 53 | display: "inline-block", 54 | "&:hover": { 55 | border: "1px solid #b0b0b0", 56 | }, 57 | }); 58 | 59 | export const ValueBox = styled(Box)({ 60 | borderRadius: 10, 61 | padding: "10px 14px", 62 | whiteSpace: "break-spaces", 63 | wordBreak: "break-all", 64 | "&:hover": { 65 | background: "#f8f8f8", 66 | }, 67 | }); 68 | 69 | export const ParameterInput = styled("input")({ 70 | display: "flex", 71 | alignItems: "center", 72 | paddingLeft: 10, 73 | width: "100%", 74 | boxSizing: "border-box", 75 | height: 40, 76 | background: "#FFFFFF", 77 | border: "1px solid #D8D8D8", 78 | borderRadius: "12px", 79 | fontSize: 14, 80 | fontFamily: "Mulish", 81 | outline: "none", 82 | "&:hover": { 83 | border: "1px solid #b0b0b0", 84 | }, 85 | "&:focus": { 86 | border: "1px solid #807e7e", 87 | }, 88 | }); 89 | 90 | export const CustomGetterInput = styled("input")({ 91 | display: "flex", 92 | alignItems: "center", 93 | paddingLeft: 14, 94 | boxSizing: "border-box", 95 | height: 34, 96 | background: "#FFFFFF", 97 | border: "1px solid #D8D8D8", 98 | borderRadius: "12px", 99 | fontSize: 14, 100 | fontFamily: "Mulish", 101 | outline: "none", 102 | "&:hover": { 103 | border: "1px solid #b0b0b0", 104 | }, 105 | "&:focus": { 106 | border: "1px solid #807e7e", 107 | }, 108 | }); 109 | -------------------------------------------------------------------------------- /src/components/HintItem.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "@mui/material"; 2 | import { styled } from "@mui/material/styles"; 3 | import { CompileResult, Hints, useSubmitSources } from "../lib/useSubmitSources"; 4 | import { TELEGRAM_SUPPORT_LINK } from "./Footer"; 5 | 6 | const _HintItem = styled("li")({ 7 | maxWidth: 650, 8 | fontSize: 14, 9 | fontWeight: 400, 10 | marginBottom: 10, 11 | }); 12 | 13 | function hintToElem(hint: Hints, compileResult: CompileResult | undefined) { 14 | switch (hint) { 15 | case Hints.ENTRYPOINT_MISSING: 16 | return "There usually should be at least one file containing an entrypoint (recv_internal, main)"; 17 | case Hints.STDLIB_ORDER: 18 | return "stdlib.fc should usually be the first file in the list (unless it's imported from another file)"; 19 | case Hints.STDLIB_MISSING: 20 | return "You can try to add stdlib.fc to your sources."; 21 | case Hints.NOT_SIMILAR: 22 | return "Source code compiles correctly but does not match the on-chain contract hash. Make sure you are using the correct compiler version, command line and file order."; 23 | case Hints.FILE_ORDER: 24 | return "Make sure all files in the command line are in the correct order"; 25 | case Hints.COMPILER_VERSION: 26 | return "Try to use the same compiler version as the contract was compiled with"; 27 | case Hints.REQUIRED_FILES: 28 | return "Make sure all required files are included in the command line"; 29 | case Hints.SUPPORT_GROUP: 30 | return ( 31 |
32 | If you are still facing issues, you can use the{" "} 33 | 40 | Telegram support group 41 | 42 |
43 | ); 44 | } 45 | } 46 | 47 | export const HintItem = ({ hint }: { hint: Hints }) => { 48 | const { data: submitSourcesData } = useSubmitSources(); 49 | return <_HintItem>{hintToElem(hint, submitSourcesData?.result.compileResult)}; 50 | }; 51 | -------------------------------------------------------------------------------- /src/components/HoverableIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Box, Link, styled } from "@mui/material"; 3 | 4 | interface IconProps { 5 | iconUrl: string; 6 | hoveredIconUrl: string; 7 | disabled?: boolean; 8 | } 9 | 10 | const Icon = styled(Box)((props: IconProps) => ({ theme }) => ({ 11 | display: "flex", 12 | alignItems: "center", 13 | justifyContent: "center", 14 | width: theme.spacing(3), 15 | height: theme.spacing(3), 16 | background: `url(${props.iconUrl})`, 17 | "&:hover": { 18 | transitionDuration: ".25s", 19 | background: `url(${props.disabled ? props.iconUrl : props.hoveredIconUrl})`, 20 | cursor: props.disabled ? "cursor" : "pointer", 21 | }, 22 | })); 23 | 24 | interface HoverableIconProps extends IconProps { 25 | link: string; 26 | } 27 | 28 | export const HoverableIcon: React.FC = ({ iconUrl, hoveredIconUrl, link }) => { 29 | return !link.length ? ( 30 | 31 | ) : ( 32 | 33 | 34 | 35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /src/components/InfoPiece.css: -------------------------------------------------------------------------------- 1 | /*TODO delete*/ 2 | .InfoPiece { 3 | display: flex; 4 | gap: 10px; 5 | } 6 | 7 | .InfoPiece-Label { 8 | font-weight: 700; 9 | min-width: 112px; 10 | /* TODO denis table-like behavior */ 11 | } 12 | 13 | .InfoPiece-Data { 14 | color: #728a96; 15 | } 16 | -------------------------------------------------------------------------------- /src/components/InfoPiece.tsx: -------------------------------------------------------------------------------- 1 | //TODO replace with DataRow 2 | import "./InfoPiece.css"; 3 | 4 | function InfoPiece({ label, data }: { label: string; data: string }) { 5 | return ( 6 |
7 |
{label}
8 |
{data}
9 |
10 | ); 11 | } 12 | 13 | export default InfoPiece; 14 | -------------------------------------------------------------------------------- /src/components/LatestVerifiedContracts.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Skeleton, styled, Typography } from "@mui/material"; 2 | import { useLoadLatestVerified } from "../lib/useLoadLatestVerified"; 3 | import { useRef } from "react"; 4 | import { useNavigatePreserveQuery } from "../lib/useNavigatePreserveQuery"; 5 | 6 | const Contract = styled(Box)(({ theme }) => ({ 7 | background: "white", 8 | padding: "16px 20px", 9 | borderRadius: 10, 10 | boxShadow: "rgb(114 138 150 / 8%) 0px 2px 16px", 11 | border: "0.5px solid rgba(114, 138, 150, 0.24)", 12 | cursor: "pointer", 13 | [theme.breakpoints.down("sm")]: { 14 | width: 280, 15 | }, 16 | })); 17 | 18 | const ContractsWrapper = styled(Box)(({ theme }) => ({ 19 | maxWidth: 1160, 20 | width: "calc(100% - 50px)", 21 | paddingTop: 20, 22 | margin: "0 auto", 23 | })); 24 | 25 | const ContractsList = styled(Box)({ 26 | display: "flex", 27 | flexDirection: "row", 28 | flexWrap: "wrap", 29 | gap: 24, 30 | margin: "0 auto", 31 | justifyContent: "left", 32 | overflow: "auto", 33 | marginTop: 24, 34 | "-webkit-text-size-adjust": "100%", 35 | }); 36 | 37 | const AddressText = styled(Box)({ 38 | overflow: "hidden", 39 | textOverflow: "ellipsis", 40 | whiteSpace: "nowrap", 41 | fontSize: 16, 42 | color: "#728A96", 43 | }); 44 | 45 | const CompilerText = styled(Box)({ 46 | marginLeft: "auto", 47 | fontSize: 14, 48 | background: "#F0F0F099", 49 | color: "#728A9699", 50 | padding: "2px 12px", 51 | borderRadius: 4, 52 | }); 53 | 54 | export function LatestVerifiedContracts() { 55 | const { data: latestVerifiedContracts, isLoading } = useLoadLatestVerified(); 56 | const navigate = useNavigatePreserveQuery(); 57 | const skeletons = useRef(new Array(30).fill(null).map((_) => Math.random() * 100)); 58 | 59 | return ( 60 | 61 | 62 | Latest verified contracts 63 | 64 | 65 | {isLoading && 66 | skeletons.current.map((width: number) => ( 67 | 72 | ))} 73 | {latestVerifiedContracts?.map((contract) => ( 74 | { 76 | navigate(`/${contract.address}`); 77 | }}> 78 | {contract.address} 79 |
80 | 85 | {contract.mainFile} 86 | 87 | {contract.compiler} 88 |
89 |
90 | ))} 91 |
92 |
93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /src/components/MobileMenu.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Drawer, IconButton, styled } from "@mui/material"; 3 | import CloseRoundedIcon from "@mui/icons-material/CloseRounded"; 4 | import { Box } from "@mui/system"; 5 | import { githubLink } from "../const"; 6 | import github from "../assets/github-dark.svg"; 7 | import { AppLogo, GitLogo, LinkWrapper } from "./TopBar.styled"; 8 | import icon from "../assets/icon.svg"; 9 | import { useNavigatePreserveQuery } from "../lib/useNavigatePreserveQuery"; 10 | import { useTonAddress } from "@tonconnect/ui-react"; 11 | import { StyledTonConnectButton } from "../styles"; 12 | 13 | interface MobileMenuProps { 14 | closeMenu?: () => void; 15 | showMenu?: boolean; 16 | } 17 | 18 | export function MobileMenu({ closeMenu, showMenu }: MobileMenuProps) { 19 | const navigate = useNavigatePreserveQuery(); 20 | const address = useTonAddress(); 21 | 22 | return ( 23 | 24 | 33 | 34 | 35 | 36 | 39 |
{} : closeMenu}> 40 | 41 |
42 | 43 | Github icon 44 | GitHub 45 | 46 |
47 | navigate("/")}> 48 | App icon 49 | TON VERIFIER 50 | 51 |
52 |
53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /src/components/SearchResults.tsx: -------------------------------------------------------------------------------- 1 | import { IconButton, Typography } from "@mui/material"; 2 | import { AppButton } from "./AppButton"; 3 | import close from "../assets/close.svg"; 4 | import recentSearch from "../assets/recent-search.svg"; 5 | import { CenteringBox } from "./Common.styled"; 6 | import { SearchResultsItem, SearchResultsWrapper } from "./SearchRusults.styled"; 7 | 8 | interface HeaderSearchResultsProps { 9 | searchResults: string[]; 10 | onItemClick: (item: string) => void; 11 | onItemDelete: (e: React.MouseEvent, item: string) => void; 12 | onHistoryClear: () => void; 13 | } 14 | 15 | export const SearchResults: React.FC = ({ 16 | searchResults, 17 | onItemClick, 18 | onItemDelete, 19 | onHistoryClear, 20 | }) => { 21 | return ( 22 | 23 | {searchResults.map((result) => ( 24 | onItemClick(result)}> 25 | 26 | 27 | Search Icon 28 | 29 | {result} 30 | 31 | onItemDelete(e, result)}> 32 | Close Icon 33 | 34 | 35 | ))} 36 | 37 | 38 | Clear History 39 | 40 | 41 | 42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /src/components/SearchRusults.styled.tsx: -------------------------------------------------------------------------------- 1 | import { Box, styled } from "@mui/system"; 2 | 3 | const SearchResultsWrapper = styled(Box)(({ theme }) => ({ 4 | position: "absolute", 5 | top: "calc(100% + 10px)", 6 | left: 0, 7 | padding: `${theme.spacing(1)}, ${theme.spacing(2)}`, 8 | zIndex: 99, 9 | background: "rgba(232,233,235)", 10 | border: "0.5px solid rgba(114, 138, 150, 0.16)", 11 | borderRadius: 16, 12 | width: "100%", 13 | maxHeight: 450, 14 | overflowY: "auto", 15 | 16 | [theme.breakpoints.down("md")]: { 17 | display: "none", 18 | }, 19 | })); 20 | 21 | const SearchResultsItem = styled(Box)({ 22 | display: "flex", 23 | alignItems: "center", 24 | justifyContent: "space-between", 25 | background: "transparent", 26 | fontSize: 20, 27 | color: "#000", 28 | fontWeight: 500, 29 | height: 30, 30 | padding: "20px 21px", 31 | transitionDuration: ".15s", 32 | "&:hover": { 33 | cursor: "pointer", 34 | background: "rgb(225,227,230)", 35 | }, 36 | }); 37 | 38 | export { SearchResultsItem, SearchResultsWrapper }; 39 | -------------------------------------------------------------------------------- /src/components/Spacer.tsx: -------------------------------------------------------------------------------- 1 | // TODO delete 2 | function Spacer({ space }: { space: number }) { 3 | return
; 4 | } 5 | 6 | export default Spacer; 7 | -------------------------------------------------------------------------------- /src/components/TestnetBar.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Typography } from "@mui/material"; 2 | import { useNavigate, useSearchParams } from "react-router-dom"; 3 | import { FlexBoxRow } from "./Getters.styled"; 4 | 5 | export function useSwitchNetwork() { 6 | const navigate = useNavigate(); 7 | const [urlParams, setUrlParams] = useSearchParams(); 8 | 9 | return () => { 10 | urlParams.has("testnet") ? urlParams.delete("testnet") : urlParams.append("testnet", ""); 11 | setUrlParams(urlParams); 12 | navigate(0); 13 | }; 14 | } 15 | 16 | export function TestnetBar() { 17 | const switchNetwork = useSwitchNetwork(); 18 | return ( 19 | 20 | 21 | Testnet 22 | { 24 | switchNetwork(); 25 | }} 26 | sx={{ cursor: "pointer" }}> 27 | Switch to mainnet 28 | 29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/components/TopBar.styled.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from "@mui/material/styles"; 2 | import { Box, Link } from "@mui/material"; 3 | import { contentMaxWidth } from "../const"; 4 | import { CenteringBox } from "./Common.styled"; 5 | 6 | const expandedHeaderHeight = 250; 7 | const headerHeight = 188; 8 | 9 | interface TopBarWrapperProps { 10 | showExpanded: boolean; 11 | isMobile: boolean; 12 | } 13 | 14 | const TopBarWrapper = styled(Box)(({ theme }) => (props: TopBarWrapperProps) => ({ 15 | display: props.isMobile ? "flex" : "inherit", 16 | alignItems: props.isMobile ? "center" : "inherit", 17 | fontWight: 700, 18 | color: "#fff", 19 | minHeight: props.isMobile ? 80 : headerHeight, 20 | height: 21 | props.showExpanded && !props.isMobile 22 | ? expandedHeaderHeight 23 | : props.isMobile 24 | ? 80 25 | : headerHeight, 26 | background: "#fff", 27 | borderBottomLeftRadius: theme.spacing(6), 28 | borderBottomRightRadius: theme.spacing(6), 29 | border: "0.5px solid rgba(114, 138, 150, 0.24)", 30 | boxShadow: "rgb(114 138 150 / 8%) 0px 2px 16px", 31 | })); 32 | 33 | const ContentColumn = styled(CenteringBox)(() => ({ 34 | gap: 10, 35 | })); 36 | 37 | const LinkWrapper = styled(Link)(() => ({ 38 | display: "flex", 39 | alignItems: "center", 40 | gap: 10, 41 | color: "#000", 42 | textDecoration: "none", 43 | cursor: "pointer", 44 | })); 45 | 46 | const TopBarContent = styled(CenteringBox)(({ theme }) => ({ 47 | margin: "auto", 48 | maxWidth: contentMaxWidth, 49 | height: 100, 50 | width: "100%", 51 | justifyContent: "space-between", 52 | gap: 10, 53 | })); 54 | 55 | const AppLogo = styled("h4")(({ theme }) => ({ 56 | color: "#000", 57 | fontSize: 20, 58 | fontWeight: 800, 59 | [theme.breakpoints.down("sm")]: { 60 | fontSize: 16, 61 | }, 62 | })); 63 | const GitLogo = styled("h5")(() => ({ 64 | color: "#000", 65 | fontWeight: 700, 66 | fontSize: 18, 67 | })); 68 | 69 | const TopBarHeading = styled("h3")(({ theme }) => ({ 70 | color: "#000", 71 | fontSize: 26, 72 | marginTop: 0, 73 | textAlign: "center", 74 | fontWeight: 800, 75 | })); 76 | 77 | const SearchWrapper = styled(CenteringBox)({ 78 | margin: "auto", 79 | maxWidth: contentMaxWidth, 80 | width: "100%", 81 | }); 82 | 83 | export { 84 | SearchWrapper, 85 | LinkWrapper, 86 | AppLogo, 87 | TopBarWrapper, 88 | TopBarContent, 89 | TopBarHeading, 90 | GitLogo, 91 | ContentColumn, 92 | }; 93 | -------------------------------------------------------------------------------- /src/components/TopBar.tsx: -------------------------------------------------------------------------------- 1 | import icon from "../assets/icon.svg"; 2 | import React, { useEffect, useState } from "react"; 3 | import { useLocation } from "react-router-dom"; 4 | import github from "../assets/github-dark.svg"; 5 | import { AddressInput } from "../components/AddressInput"; 6 | import { CenteringBox } from "./Common.styled"; 7 | import { githubLink } from "../const"; 8 | import { 9 | AppLogo, 10 | ContentColumn, 11 | GitLogo, 12 | LinkWrapper, 13 | SearchWrapper, 14 | TopBarContent, 15 | TopBarHeading, 16 | TopBarWrapper, 17 | } from "./TopBar.styled"; 18 | import { IconButton, styled, useMediaQuery, useTheme } from "@mui/material"; 19 | import MenuRoundedIcon from "@mui/icons-material/MenuRounded"; 20 | import { MobileMenu } from "./MobileMenu"; 21 | import { useNavigatePreserveQuery } from "../lib/useNavigatePreserveQuery"; 22 | import { StyledTonConnectButton } from "../styles"; 23 | 24 | export function TopBar() { 25 | const { pathname } = useLocation(); 26 | 27 | const theme = useTheme(); 28 | const navigate = useNavigatePreserveQuery(); 29 | const headerSpacings = useMediaQuery(theme.breakpoints.down("lg")); 30 | const isSmallScreen = useMediaQuery(theme.breakpoints.down("md")); 31 | const [showExpanded, setShowExpanded] = useState(pathname.length === 1); 32 | const [showMenu, setShowMenu] = useState(false); 33 | 34 | useEffect(() => { 35 | setShowExpanded(pathname.length === 1); 36 | }, [pathname]); 37 | 38 | return ( 39 | 43 | {isSmallScreen && ( 44 | setShowMenu(true)}> 47 | 48 | 49 | )} 50 | {!isSmallScreen && ( 51 | 52 | navigate("/")}> 53 | App icon 54 | TON VERIFIER 55 | 56 | 57 | 58 | 59 | 60 | 61 | Github icon 62 | GitHub 63 | 64 | 65 | 66 | )} 67 | {pathname.length < 2 && !isSmallScreen && ( 68 | Smart Contract Verifier 69 | )} 70 | 71 | 72 | 73 | setShowMenu(false)} showMenu={showMenu} /> 74 | 75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /src/components/VerificationProof.tsx: -------------------------------------------------------------------------------- 1 | import { useLoadContractProof } from "../lib/useLoadContractProof"; 2 | import { useLoadVerifierRegistryInfo } from "../lib/useLoadVerifierRegistryInfo"; 3 | import { Box, Skeleton, Tab, Tabs } from "@mui/material"; 4 | import { PopupTableTitle, PopupWrapper } from "./VerificationProofPopup.styled"; 5 | import { styled } from "@mui/system"; 6 | import React, { useState } from "react"; 7 | import { InBrowserVerificationGuide, ManualVerificationGuide } from "./VerificationGuides"; 8 | 9 | export function VerificationProof() { 10 | const { data: contractProofData, isLoading } = useLoadContractProof(); 11 | const { isLoading: isLoadingVerifierRegistry } = useLoadVerifierRegistryInfo(); 12 | 13 | return ( 14 | 15 | {contractProofData && !isLoadingVerifierRegistry && ( 16 | 17 | Verify manually 18 | 19 | 20 | )} 21 | {(isLoading || isLoadingVerifierRegistry) && ( 22 | 27 | )} 28 | 29 | ); 30 | } 31 | 32 | const VerificationPanelTabs = styled(Tabs)({ 33 | borderBottom: "none", 34 | "& .MuiTabs-indicator": { 35 | borderBottom: "4px solid #0088CC", 36 | borderRadius: 20, 37 | }, 38 | "& .MuiTab-root.Mui-selected": { 39 | color: "#000", 40 | fontWeight: 800, 41 | }, 42 | }); 43 | 44 | function VerificationPanel() { 45 | // const [value, setValue] = useState(0); 46 | 47 | // const handleChange = (event: React.SyntheticEvent, newValue: number) => { 48 | // setValue(newValue); 49 | // }; 50 | 51 | return ( 52 | 53 | {/* 54 | 55 | 56 | 57 | 58 | 59 | {value === 0 && } 60 | {value === 1 && } */} 61 | 62 | 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /src/components/VerificationProofPopup.styled.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from "@mui/material/styles"; 2 | import { CenteringBox, TitleText } from "./Common.styled"; 3 | import Table from "@mui/material/Table"; 4 | import TableHead from "@mui/material/TableHead"; 5 | import TableRow from "@mui/material/TableRow"; 6 | import { BorderLessCell, HeaderCell } from "./FileTable.styled"; 7 | import { Box, Link, Typography } from "@mui/material"; 8 | 9 | const PopupTable = styled(Table)({ 10 | overflow: "scroll", 11 | background: "#F7F9FB", 12 | borderRadius: "5px", 13 | width: "100%", 14 | }); 15 | 16 | const PopupWrapper = styled(Box)({ 17 | background: "#F7F9FB", 18 | borderRadius: "5px", 19 | width: "100%", 20 | }); 21 | 22 | const PopupTableHead = styled(TableHead)({ 23 | "&.MuiTableHead-root th": { 24 | border: "none", 25 | fontSize: 13, 26 | }, 27 | }); 28 | 29 | const PopupTableHeadRow = styled(TableRow)({ 30 | fontWeight: 700, 31 | }); 32 | 33 | const PopupTableHeadCell = styled(HeaderCell)({ 34 | paddingLeft: 0, 35 | paddingBottom: "2px", 36 | }); 37 | 38 | const PopupTableHeadPaddingCell = styled(BorderLessCell)({ 39 | paddingBottom: 10, 40 | }); 41 | 42 | const VerifiedTag = styled(CenteringBox)({ 43 | width: 59, 44 | height: 21, 45 | background: "#08D088", 46 | borderRadius: 40, 47 | color: "#fff", 48 | justifyContent: "space-around", 49 | fontSize: 12, 50 | }); 51 | 52 | const PopupTableBodyCell = styled(BorderLessCell)({ 53 | paddingBottom: 16, 54 | }); 55 | 56 | const PopupLink = styled(Link)({ 57 | textDecoration: "none", 58 | cursor: "pointer", 59 | color: "#0088CC", 60 | }); 61 | 62 | const CloseButtonWrapper = styled(Box)({ 63 | width: "100%", 64 | display: "flex", 65 | justifyContent: "flex-end", 66 | }); 67 | 68 | const PopupTableTypography = styled(Typography)({ 69 | color: "#728A96", 70 | fontSize: 14, 71 | }); 72 | 73 | const PopupTableTitle = styled(TitleText)({ 74 | fontSize: 18, 75 | fontWeight: 800, 76 | color: "#000", 77 | textAlign: "center", 78 | }); 79 | 80 | const CommandLabel = styled(Box)({ 81 | display: "inline-flex", 82 | alignItems: "center", 83 | height: "20px", 84 | padding: "0 7px", 85 | background: "rgba(146, 146, 146, 0.3)", 86 | borderRadius: "10px", 87 | color: "#212121", 88 | fontWeight: 400, 89 | fontSize: "14px", 90 | fontFamily: "IBM Plex Mono, monospace", 91 | }); 92 | 93 | const CommandEllipsisLabel = styled(CommandLabel)({ 94 | position: "relative", 95 | top: 5, 96 | display: "inline-block", 97 | whiteSpace: "nowrap", 98 | lineHeight: "20px", 99 | width: "100%", 100 | maxWidth: 600, 101 | overflow: "hidden", 102 | textOverflow: "ellipsis", 103 | }); 104 | 105 | export { 106 | PopupTable, 107 | PopupTableHead, 108 | PopupTableHeadRow, 109 | PopupTableHeadCell, 110 | PopupTableHeadPaddingCell, 111 | VerifiedTag, 112 | CommandLabel, 113 | CommandEllipsisLabel, 114 | PopupTableBodyCell, 115 | PopupTableTypography, 116 | PopupTableTitle, 117 | PopupWrapper, 118 | PopupLink, 119 | CloseButtonWrapper, 120 | }; 121 | -------------------------------------------------------------------------------- /src/components/VerificationProofPopup.tsx: -------------------------------------------------------------------------------- 1 | import { AppPopup } from "./AppPopup"; 2 | import { CenteringBox, TitleText } from "./Common.styled"; 3 | import { Box, ClickAwayListener, IconButton, useMediaQuery, useTheme } from "@mui/material"; 4 | import close from "../assets/close.svg"; 5 | import verificationPopup from "../assets/verification-popup.svg"; 6 | import React from "react"; 7 | import { CloseButtonWrapper } from "./VerificationProofPopup.styled"; 8 | import { VerificationProofTable } from "./VerificationProofTable"; 9 | import { VerificationProof } from "./VerificationProof"; 10 | 11 | interface VerificationProofPopupProps { 12 | onClose: () => void; 13 | } 14 | 15 | export function VerificationProofPopup({ onClose }: VerificationProofPopupProps) { 16 | const theme = useTheme(); 17 | const headerSpacings = useMediaQuery(theme.breakpoints.down("lg")); 18 | 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | Close icon 26 | 27 | 28 | 29 | Popup icon 30 | 31 | Verification Proof 32 | 33 | 34 | 35 | {import.meta.env.VITE_SHOW_MANUAL_VERIFICATION && ( 36 | 37 | 38 | 39 | )} 40 | 41 | 42 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /src/components/VerificationProofTable.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from "react"; 2 | import { useLoadContractProof } from "../lib/useLoadContractProof"; 3 | import { useLoadVerifierRegistryInfo } from "../lib/useLoadVerifierRegistryInfo"; 4 | import useNotification from "../lib/useNotification"; 5 | import { Box } from "@mui/material"; 6 | import { PopupTable } from "./VerificationProofPopup.styled"; 7 | import { 8 | VerificationProofPopupTableDataRow, 9 | VerificationProofPopupTableHead, 10 | VerificationProofPopupTableSkeletonRow, 11 | } from "./VerificationProofPopupTable"; 12 | import TableBody from "@mui/material/TableBody"; 13 | 14 | export function VerificationProofTable() { 15 | const { 16 | data: contractProofData, 17 | isLoading: isLoadingProof, 18 | error: errorProof, 19 | } = useLoadContractProof(); 20 | const { 21 | data: verifierRegistryInfo, 22 | isLoading: isLoadingVerifierRegistry, 23 | error: errorVerifierRegistry, 24 | } = useLoadVerifierRegistryInfo(); 25 | const { showNotification } = useNotification(); 26 | 27 | // TODO this supports a single verifier Id for now. 28 | // when we wish to support multiple verifiers, load contract proof would have to address that 29 | const verifierConfig = verifierRegistryInfo?.find((v) => v.name === window.verifierId); 30 | 31 | const onCopy = useCallback(async (value: string) => { 32 | navigator.clipboard.writeText(value); 33 | showNotification("Copied to clipboard!", "success"); 34 | }, []); 35 | 36 | return ( 37 | 45 | 46 | 47 | 48 | {isLoadingProof || isLoadingVerifierRegistry ? ( 49 | <> 50 | 51 | 52 | 53 | ) : ( 54 | verifierConfig && 55 | contractProofData && 56 | Object.entries(verifierConfig.pubKeyEndpoints).map(([pubKey, endpoint]) => { 57 | return ( 58 | 67 | ); 68 | }) 69 | )} 70 | 71 | 72 | {(!!errorProof || !!errorVerifierRegistry) && 73 | `${errorProof} ${errorVerifierRegistry} (App notification)`} 74 | 75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /src/components/VerifiedSourceCode.tsx: -------------------------------------------------------------------------------- 1 | import { useLoadContractSourceCode } from "../lib/useLoadContractSourceCode"; 2 | import React from "react"; 3 | 4 | interface VerifiedSourceCodeProps { 5 | button: React.ReactNode; 6 | } 7 | 8 | export function VerifiedSourceCode({ button }: VerifiedSourceCodeProps) { 9 | useLoadContractSourceCode(); 10 | 11 | return ( 12 |
13 |
14 |
15 |
16 |
{button}
17 |
18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/components/admin/Admin.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Stack } from "@mui/material"; 2 | import { TestnetBar } from "../TestnetBar"; 3 | import SourcesRegistry from "./SourcesRegistry"; 4 | import { VerifierRegistry } from "./VerifierRegistry"; 5 | import { FlexBoxRow } from "../Getters.styled"; 6 | import { Footer } from "../Footer"; 7 | import { StyledTonConnectButton } from "../../styles"; 8 | 9 | export function Admin() { 10 | return ( 11 |
12 | {window.isTestnet && } 13 | 14 |

Admin

15 | 16 |
17 | 18 | 19 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/admin/ContractInteract.tsx: -------------------------------------------------------------------------------- 1 | import { Button, TextField } from "@mui/material"; 2 | import { useState } from "react"; 3 | import { Address, beginCell, Builder } from "ton"; 4 | import ConnectButton from "../ConnectButton"; 5 | import Spacer from "../Spacer"; 6 | 7 | function CellBuilder() { 8 | const [state, setState] = useState<{ 9 | builder: Builder; 10 | spec: any[]; 11 | }>({ 12 | spec: [], 13 | builder: beginCell(), 14 | }); 15 | 16 | return ( 17 |
18 |

Build the cell

19 | { 25 | if (e.code === "Enter") { 26 | // @ts-ignore 27 | const [num, size] = e.target.value.split(","); 28 | setState((state) => { 29 | return { 30 | spec: [ 31 | ...state.spec, 32 | { 33 | type: "uint" + size, 34 | value: num, 35 | }, 36 | ], 37 | builder: state.builder.storeUint(num, size), 38 | }; 39 | }); 40 | // @ts-ignore 41 | e.target.value = ""; 42 | } 43 | }} 44 | /> 45 | { 51 | if (e.code === "Enter") { 52 | // @ts-ignore 53 | const addressStr = e.target.value; 54 | setState((state) => { 55 | return { 56 | spec: [ 57 | ...state.spec, 58 | { 59 | type: "address", 60 | value: addressStr, 61 | }, 62 | ], 63 | builder: state.builder.storeAddress( 64 | // @ts-ignore 65 | Address.parse(addressStr), 66 | ), 67 | }; 68 | }); 69 | // @ts-ignore 70 | e.target.value = ""; 71 | } 72 | }} 73 | /> 74 |
{JSON.stringify(state.spec)}
75 | 76 | 77 |
78 | ); 79 | } 80 | 81 | function ContractInteract() { 82 | return ( 83 |
89 | 90 | 91 | 92 | 93 | 94 |
95 |

OPs

96 | 97 | 98 |
99 |
100 | ); 101 | } 102 | 103 | export default ContractInteract; 104 | -------------------------------------------------------------------------------- /src/components/admin/form/TextField.tsx: -------------------------------------------------------------------------------- 1 | import { Controller, Control } from "react-hook-form"; 2 | import { TextField as MuiTextField, TextFieldProps as MuiTextFieldProps } from "@mui/material"; 3 | 4 | type TextFieldProps = Omit & { 5 | name: string; 6 | control?: Control; 7 | }; 8 | 9 | export function TextField({ label, name, control }: TextFieldProps) { 10 | return ( 11 | ( 15 | 23 | )}> 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/components/tactDeployer/TactDeployer.styled.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from "@mui/material"; 2 | 3 | export const CustomValueInput = styled("input")({ 4 | display: "flex", 5 | alignItems: "center", 6 | paddingLeft: 14, 7 | boxSizing: "border-box", 8 | height: 34, 9 | background: "#FFFFFF", 10 | border: "1px solid #D8D8D8", 11 | borderRadius: "12px", 12 | fontSize: 14, 13 | fontFamily: "Mulish", 14 | outline: "none", 15 | "&:hover": { 16 | border: "1px solid #b0b0b0", 17 | }, 18 | "&:focus": { 19 | border: "1px solid #807e7e", 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /src/components/tactDeployer/TopBar.tsx: -------------------------------------------------------------------------------- 1 | import icon from "../../assets/icon.svg"; 2 | import github from "../../assets/github-dark.svg"; 3 | import { Box, useMediaQuery, useTheme } from "@mui/material"; 4 | import { CenteringBox } from "../Common.styled"; 5 | import { githubLink } from "../../const"; 6 | import { TopBarContent, LinkWrapper, AppLogo, ContentColumn, GitLogo } from "../TopBar.styled"; 7 | import { styled } from "@mui/material/styles"; 8 | import { StyledTonConnectButton } from "../../styles"; 9 | 10 | interface TopBarWrapperProps { 11 | isMobile: boolean; 12 | } 13 | 14 | const TopBarWrapper = styled(Box)(({ theme }) => (props: TopBarWrapperProps) => ({ 15 | display: props.isMobile ? "flex" : "inherit", 16 | alignItems: props.isMobile ? "center" : "inherit", 17 | fontWight: 700, 18 | color: "#fff", 19 | height: props.isMobile ? 90 : 100, 20 | background: "#fff", 21 | borderBottomLeftRadius: theme.spacing(6), 22 | borderBottomRightRadius: theme.spacing(6), 23 | border: "0.5px solid rgba(114, 138, 150, 0.24)", 24 | boxShadow: "rgb(114 138 150 / 8%) 0px 2px 16px", 25 | })); 26 | 27 | export function TopBar() { 28 | const theme = useTheme(); 29 | const isSmallScreen = useMediaQuery(theme.breakpoints.down("md")); 30 | const headerSpacings = useMediaQuery(theme.breakpoints.down("lg")); 31 | 32 | return ( 33 | 34 | 35 | 36 | App icon 37 | TACT DEPLOYER 38 | 39 | 40 | 41 | 42 | 43 | {!isSmallScreen && ( 44 | 45 | Github icon 46 | GitHub 47 | 48 | )} 49 | 50 | 51 | 52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /src/components/usePublishStepsStore.ts: -------------------------------------------------------------------------------- 1 | import create from "zustand"; 2 | 3 | export const usePublishStepsStore = create<{ 4 | isPublishExpanded: boolean; 5 | isAddSourcesExpanded: boolean; 6 | setPublishExpanded: (isExpanded: boolean) => void; 7 | setAddSourcesExpanded: (isExpanded: boolean) => void; 8 | }>((set) => ({ 9 | isPublishExpanded: false, 10 | isAddSourcesExpanded: true, 11 | setPublishExpanded: (isExpanded: boolean) => set({ isPublishExpanded: isExpanded }), 12 | setAddSourcesExpanded: (isExpanded: boolean) => set({ isAddSourcesExpanded: isExpanded }), 13 | })); 14 | -------------------------------------------------------------------------------- /src/const.ts: -------------------------------------------------------------------------------- 1 | const contentMaxWidth = 1160; 2 | const animationTimeout = 250; 3 | 4 | const githubLink = "https://github.com/orbs-network/ton-contract-verifier"; 5 | 6 | const SEARCH_HISTORY = "searchHistory"; 7 | 8 | export { contentMaxWidth, animationTimeout, githubLink, SEARCH_HISTORY }; 9 | -------------------------------------------------------------------------------- /src/hooks.ts: -------------------------------------------------------------------------------- 1 | import { SendTransactionRequest, useTonConnectUI } from "@tonconnect/ui-react"; 2 | import { Cell, StateInit, beginCell, storeStateInit } from "ton"; 3 | 4 | export const useRequestTXN = () => { 5 | const [tonConnection] = useTonConnectUI(); 6 | return async ( 7 | to: string, 8 | value: bigint, 9 | message?: Cell, 10 | stateInit?: StateInit, 11 | ): Promise<"issued" | "rejected"> => { 12 | try { 13 | let cell; 14 | if (stateInit) { 15 | const builder = beginCell(); 16 | storeStateInit(stateInit)(builder); 17 | cell = builder.asCell(); 18 | } 19 | 20 | const tx: SendTransactionRequest = { 21 | validUntil: Date.now() + 5 * 60 * 1000, 22 | messages: [ 23 | { 24 | address: to, 25 | amount: value.toString(), 26 | stateInit: cell ? cell.toBoc().toString("base64") : undefined, 27 | payload: message?.toBoc().toString("base64"), 28 | }, 29 | ], 30 | }; 31 | await tonConnection.sendTransaction(tx); 32 | return "issued"; 33 | } catch (e) { 34 | console.error(e); 35 | return "rejected"; 36 | } 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | /* :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | color-scheme: light dark; 8 | color: rgba(255, 255, 255, 0.87); 9 | background-color: #242424; 10 | 11 | font-synthesis: none; 12 | text-rendering: optimizeLegibility; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | -webkit-text-size-adjust: 100%; 16 | } 17 | 18 | a { 19 | font-weight: 500; 20 | color: #646cff; 21 | text-decoration: inherit; 22 | } 23 | a:hover { 24 | color: #535bf2; 25 | } 26 | 27 | body { 28 | margin: 0; 29 | display: flex; 30 | place-items: center; 31 | min-width: 320px; 32 | min-height: 100vh; 33 | } 34 | 35 | h1 { 36 | font-size: 3.2em; 37 | line-height: 1.1; 38 | } 39 | 40 | button { 41 | border-radius: 8px; 42 | border: 1px solid transparent; 43 | padding: 0.6em 1.2em; 44 | font-size: 1em; 45 | font-weight: 500; 46 | font-family: inherit; 47 | background-color: #1a1a1a; 48 | cursor: pointer; 49 | transition: border-color 0.25s; 50 | } 51 | button:hover { 52 | border-color: #646cff; 53 | } 54 | button:focus, 55 | button:focus-visible { 56 | outline: 4px auto -webkit-focus-ring-color; 57 | } */ 58 | 59 | /* 60 | @media (prefers-color-scheme: light) { 61 | :root { 62 | color: #213547; 63 | background-color: #ffffff; 64 | } 65 | a:hover { 66 | color: #747bff; 67 | } 68 | button { 69 | background-color: #f9f9f9; 70 | } 71 | } */ 72 | -------------------------------------------------------------------------------- /src/lib/downloadSources.ts: -------------------------------------------------------------------------------- 1 | import JSZip from "jszip"; 2 | import FileSaver from "file-saver"; 3 | 4 | export function downloadSources( 5 | files: { 6 | name: string; 7 | content: string; 8 | }[], 9 | ) { 10 | const zip = new JSZip(); 11 | files.map((f) => zip.file(f.name, f.content)); 12 | zip.generateAsync({ type: "blob" }).then(function (content) { 13 | FileSaver.saveAs(content, "sources.zip"); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /src/lib/getAdmin.ts: -------------------------------------------------------------------------------- 1 | import { Address, Cell, TonClient } from "ton"; 2 | import { SourcesRegistry as SourcesRegistryContract } from "./wrappers/sources-registry"; 3 | 4 | export async function getAdmin(sourcesRegistry: Address, tonClient: TonClient) { 5 | const contract = tonClient.open(SourcesRegistryContract.createFromAddress(sourcesRegistry)); 6 | const adminAddress = await contract.getAdminAddress(); 7 | return adminAddress?.toString(); 8 | } 9 | -------------------------------------------------------------------------------- /src/lib/getClient.ts: -------------------------------------------------------------------------------- 1 | import { TonClient } from "ton"; 2 | import { getHttpEndpoint } from "@orbs-network/ton-access"; 3 | 4 | declare global { 5 | interface Window { 6 | isTestnet: boolean; 7 | verifierRegistryAddress: string; 8 | sourcesRegistryAddress: string; 9 | verifierId: string; 10 | } 11 | } 12 | 13 | window.isTestnet = new URLSearchParams(window.location.search).has("testnet"); 14 | 15 | window.sourcesRegistryAddress = window.isTestnet 16 | ? import.meta.env.VITE_SOURCES_REGISTRY_TESTNET 17 | : import.meta.env.VITE_SOURCES_REGISTRY; 18 | 19 | window.verifierId = window.isTestnet 20 | ? import.meta.env.VITE_VERIFIER_ID_TESTNET 21 | : import.meta.env.VITE_VERIFIER_ID; 22 | 23 | const endpointP = getHttpEndpoint({ network: window.isTestnet ? "testnet" : "mainnet" }); 24 | 25 | export async function getEndpoint() { 26 | return endpointP; 27 | } 28 | 29 | async function _getClient() { 30 | return new TonClient({ 31 | endpoint: await getEndpoint(), 32 | }); 33 | } 34 | 35 | const clientP = _getClient(); 36 | 37 | export async function getClient() { 38 | return clientP; 39 | } 40 | -------------------------------------------------------------------------------- /src/lib/getter/getterParser.ts: -------------------------------------------------------------------------------- 1 | import Parser from "web-tree-sitter"; 2 | import { isWebAssemblySupported } from "../../utils/generalUtils"; 3 | import { sendAnalyticsEvent, AnalyticsAction } from "../googleAnalytics"; 4 | 5 | let language: Parser.Language; 6 | 7 | export const initParser = async (treeSitterUri: string, langUri: string) => { 8 | if (language) { 9 | return; 10 | } 11 | const options: object | undefined = { 12 | locateFile() { 13 | return treeSitterUri; 14 | }, 15 | }; 16 | await Parser.init(options); 17 | language = await Parser.Language.load(langUri); 18 | }; 19 | 20 | export const createParser = () => { 21 | const parser = new Parser(); 22 | parser.setLanguage(language); 23 | parser.setTimeoutMicros(1000 * 1000); 24 | return parser; 25 | }; 26 | 27 | type GetterParameter = { 28 | type: string; 29 | name: string; 30 | }; 31 | 32 | export type Getter = { 33 | returnTypes: string[]; 34 | name: string; 35 | parameters: GetterParameter[]; 36 | }; 37 | 38 | export async function parseGetters(code: string): Promise { 39 | if (!isWebAssemblySupported()) { 40 | return []; 41 | } 42 | 43 | sendAnalyticsEvent(AnalyticsAction.GETTER_PARSE_START); 44 | 45 | await initParser("./tree-sitter.wasm", "./tree-sitter-func.wasm"); 46 | const p = createParser(); 47 | const parsed = p.parse(code); 48 | 49 | const getters = parsed.rootNode.children.filter( 50 | (c) => 51 | c.type === "function_definition" && 52 | c.children.find((n) => n.type === "specifiers_list")?.text.includes("method_id"), 53 | ); 54 | 55 | const gettersParsed = getters.map((f) => { 56 | const returnTypes = f.children[0].children 57 | .filter((c) => !c.type.match(/[,()]/)) // TODO types are slice, primitive_type, ",", "(", ")" 58 | .map((c) => c.text); 59 | 60 | const name = f.children.find((n) => n.type === "function_name")!.text; 61 | 62 | const parameters = f.children 63 | .find((n) => n.type === "parameter_list")! 64 | .children.filter((c) => c.type === "parameter_declaration") 65 | .map((c) => ({ 66 | type: c.child(0)!.text, 67 | name: c.child(1)!.text, 68 | })); 69 | 70 | return { 71 | returnTypes, 72 | name, 73 | parameters, 74 | }; 75 | }); 76 | 77 | return gettersParsed; 78 | } 79 | -------------------------------------------------------------------------------- /src/lib/getter/useCustomGetter.ts: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | import { StateGetter, Parameter } from "./useGetters"; 3 | import { immer } from "zustand/middleware/immer"; 4 | 5 | export type CustomStateGetter = StateGetter & { 6 | parameters: CustomParameter[]; 7 | setName: (val: string) => void; 8 | addParameter: () => void; 9 | removeParameter: () => void; 10 | clear: () => void; 11 | }; 12 | 13 | type CustomParameter = Parameter & { 14 | _id: number; 15 | setName: (name: string) => void; 16 | }; 17 | 18 | export const _useCustomGetter = create( 19 | immer((set, get) => ({ 20 | name: "", 21 | setName: (val: string) => { 22 | set((state) => { 23 | state.name = val; 24 | }); 25 | }, 26 | parameters: [], 27 | addParameter: () => { 28 | set((state) => { 29 | const _id = Math.random(); 30 | state.parameters.push({ 31 | name: "", 32 | _id, 33 | possibleTypes: ["int", "slice", "address"], 34 | selectedTypeIdx: 0, 35 | setValue: (val) => 36 | (state.parameters.find((p: CustomParameter) => p._id === _id)!.value = val), 37 | setName: (val: string) => { 38 | set((state) => { 39 | state.parameters.find((p: CustomParameter) => p._id === _id)!.name = val; 40 | }); 41 | }, 42 | toggleNextType: () => { 43 | set((state) => { 44 | const param = state.parameters.find((p: CustomParameter) => p._id === _id)!; 45 | param.selectedTypeIdx = (param.selectedTypeIdx + 1) % param.possibleTypes.length; 46 | }); 47 | }, 48 | type: () => { 49 | const param = get().parameters.find((p: CustomParameter) => p._id === _id)!; 50 | return param.possibleTypes[param.selectedTypeIdx]; 51 | }, 52 | 53 | originalType: () => { 54 | const param = get().parameters.find((p: CustomParameter) => p._id === _id)!; 55 | return param.possibleTypes[0]; 56 | }, 57 | value: "", 58 | }); 59 | }); 60 | }, 61 | returnTypes: [], 62 | removeParameter: () => { 63 | set((state) => { 64 | state.parameters.pop(); 65 | }); 66 | }, 67 | clear: () => { 68 | set((state) => { 69 | state.name = ""; 70 | state.parameters = []; 71 | }); 72 | }, 73 | })), 74 | ); 75 | 76 | export function useCustomGetter() { 77 | const customGetter = _useCustomGetter(); 78 | return customGetter; 79 | } 80 | -------------------------------------------------------------------------------- /src/lib/getter/useQueryGetter.ts: -------------------------------------------------------------------------------- 1 | import { useMutation } from "@tanstack/react-query"; 2 | import { Address, Cell, fromNano } from "ton"; 3 | import { getClient } from "../getClient"; 4 | import { sendAnalyticsEvent, AnalyticsAction } from "../googleAnalytics"; 5 | import { makeGetCall } from "../makeGetCall"; 6 | import { useContractAddress } from "../useContractAddress"; 7 | import { useGetters, StateGetter } from "./useGetters"; 8 | import { beginCell } from "ton"; 9 | 10 | export type PossibleRepresentation = "address" | "coins" | "base64" | "boc" | "int" | "raw" | "hex"; 11 | 12 | export type GetterResponseValue = { type: PossibleRepresentation; value: string }; 13 | 14 | export function useQueryGetter(getter: StateGetter) { 15 | const { contractAddress } = useContractAddress(); 16 | const { getters } = useGetters(); 17 | 18 | return useMutation([contractAddress, "getter", getter.name], async () => { 19 | const tc = await getClient(); 20 | if (!contractAddress) return; 21 | if (!getters) return; 22 | 23 | sendAnalyticsEvent(AnalyticsAction.RUN_GETTER); 24 | 25 | const resp = makeGetCall( 26 | Address.parse(contractAddress), 27 | getter.name, 28 | getter.parameters.map((param) => { 29 | const type = param.possibleTypes[param.selectedTypeIdx]; 30 | switch (type) { 31 | case "int": 32 | return BigInt(param.value); 33 | case "address": 34 | return beginCell().storeAddress(Address.parse(param.value)).endCell(); 35 | default: 36 | return Cell.fromBoc(Buffer.from(param.value, "base64"))[0]; 37 | } 38 | }), 39 | (s) => { 40 | return s.map((value) => { 41 | const possibleRepresentations: { type: PossibleRepresentation; value: string }[] = []; 42 | if (value instanceof Cell) { 43 | try { 44 | if (value.beginParse().remainingBits === 267) { 45 | possibleRepresentations.push({ 46 | type: "address", 47 | value: value.beginParse().loadAddress()!.toString(), 48 | }); 49 | } 50 | } catch (e) { 51 | // Ignore 52 | } 53 | 54 | possibleRepresentations.push({ 55 | type: "base64", 56 | value: value.toBoc().toString("base64"), 57 | }); 58 | possibleRepresentations.push({ type: "boc", value: value.toString() }); 59 | } else if (typeof value === "bigint") { 60 | possibleRepresentations.push({ type: "int", value: value.toString() }); 61 | possibleRepresentations.push({ type: "coins", value: fromNano(value) }); 62 | possibleRepresentations.push({ type: "hex", value: value.toString(16) }); 63 | possibleRepresentations.push({ 64 | type: "base64", 65 | value: Buffer.from(value.toString(16), "hex").toString("base64"), 66 | }); 67 | } else { 68 | possibleRepresentations.push({ type: "raw", value: String(value) }); 69 | } 70 | 71 | return possibleRepresentations as GetterResponseValue[]; 72 | }); 73 | }, 74 | tc, 75 | ); 76 | 77 | return resp; 78 | }); 79 | } 80 | -------------------------------------------------------------------------------- /src/lib/googleAnalytics.ts: -------------------------------------------------------------------------------- 1 | import ReactGA from "react-ga4"; 2 | 3 | export enum AnalyticsAction { 4 | ADD_FILE = "ADD_FILE", 5 | CONNECT_WALLET_POPUP = "CONNECT_WALLET_POPUP", 6 | WALLET_CONNECTED = "WALLET_CONNECTED", 7 | SELECT_WALLET = "SELECT_WALLET", 8 | COMPILE_SUBMIT = "COMPILE_SUBMIT", 9 | COMPILE_SERVER_ERROR = "COMPILE_SERVER_ERROR", 10 | SIGN_SERVER_ERROR = "SIGN_SERVER_ERROR", 11 | SIGN_SERVER_SUCCESS = "SIGN_SERVER_SUCCESS", 12 | COMPILE_HASHES_NOT_SIMILAR = "COMPILE_HASHES_NOT_SIMILAR", 13 | COMPILE_COMPILATION_ERROR = "COMPILE_COMPILATION_ERROR", 14 | COMPILE_SUCCESS_HASHES_MATCH = "COMPILE_SUCCESS_HASHES_MATCH", 15 | PUBLISH_CLICK = "PUBLISH_CLICK", 16 | TRANSACTION_ISSUED = "TRANSACTION_ISSUED", 17 | TRANSACTION_REJECTED = "TRANSACTION_REJECTED", 18 | TRANSACTION_ERROR = "TRANSACTION_ERROR", 19 | TRANSACTION_EXPIRED = "TRANSACTION_EXPIRED", 20 | CONTRACT_DEPLOYED = "CONTRACT_DEPLOYED", 21 | IN_BROWSER_COMPILE_ERROR = "IN_BROWSER_COMPILE_ERROR", 22 | IN_BROWSER_COMPILE_START = "IN_BROWSER_COMPILE_START", 23 | IN_BROWSER_COMPILE_SUCCESS = "IN_BROWSER_COMPILE_SUCCESS", 24 | GETTER_PARSE_START = "GETTER_PARSE_START", 25 | RUN_GETTER = "RUN_GETTER", 26 | } 27 | 28 | export const sendAnalyticsEvent = (action: AnalyticsAction, label: string = "") => { 29 | if (!ReactGA.isInitialized) { 30 | return; 31 | } 32 | try { 33 | ReactGA.event({ 34 | category: "VERIFIER", 35 | action, 36 | label, 37 | }); 38 | } catch (error) { 39 | console.log(error); 40 | } 41 | }; 42 | 43 | export const initGA = () => { 44 | try { 45 | ReactGA.initialize(import.meta.env.VITE_APP_GA!); 46 | ReactGA.send(window.location.pathname + window.location.search); 47 | } catch (error) {} 48 | }; 49 | -------------------------------------------------------------------------------- /src/lib/makeGetCall.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Address, 3 | Cell, 4 | TonClient, 5 | TupleBuilder, 6 | TupleItemCell, 7 | TupleReader, 8 | Tuple, 9 | TupleItemInt, 10 | } from "ton"; 11 | 12 | function _prepareParams(params: any[] = []) { 13 | const paramsTuple = new TupleBuilder(); 14 | params.forEach((p) => { 15 | if (p instanceof Cell) { 16 | paramsTuple.writeSlice(p); 17 | } else if (typeof p === "bigint") { 18 | paramsTuple.writeNumber(p); 19 | } else { 20 | throw new Error("unknown type!"); 21 | } 22 | }); 23 | return paramsTuple.build(); 24 | } 25 | 26 | type GetResponseValue = Cell | bigint | null; 27 | 28 | function _parseGetMethodCall(stack: TupleReader): GetResponseValue[] { 29 | const parsedItems: GetResponseValue[] = []; 30 | while (stack.remaining) { 31 | const item = stack.pop(); 32 | switch (item.type) { 33 | case "int": { 34 | parsedItems.push((item as TupleItemInt).value); 35 | break; 36 | } 37 | case "cell": { 38 | parsedItems.push((item as TupleItemCell).cell); 39 | break; 40 | } 41 | case "tuple": { 42 | if ((item as Tuple).items.length === 0) { 43 | parsedItems.push(null); 44 | } else { 45 | throw new Error("list parsing not supported"); 46 | } 47 | break; 48 | } 49 | default: { 50 | throw new Error(`unknown type: ${item.type}`); 51 | } 52 | } 53 | } 54 | return parsedItems; 55 | } 56 | 57 | export async function makeGetCall( 58 | address: Address | undefined, 59 | name: string, 60 | params: any[], 61 | parser: (stack: GetResponseValue[]) => T, 62 | tonClient: TonClient, 63 | ) { 64 | const { stack } = await tonClient.runMethod(address!, name, _prepareParams(params)); 65 | return parser(_parseGetMethodCall(stack)); 66 | } 67 | -------------------------------------------------------------------------------- /src/lib/useAddressHistory.ts: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect } from "react"; 2 | import { useAddressInput } from "./useAddressInput"; 3 | import { persist } from "zustand/middleware"; 4 | import create from "zustand"; 5 | import { useContractAddress } from "./useContractAddress"; 6 | import { useNavigatePreserveQuery } from "./useNavigatePreserveQuery"; 7 | 8 | interface AddressHistoryState { 9 | addresses: string[]; 10 | addAddress: (address: string) => void; 11 | clear: () => void; 12 | removeItem: (address: string) => void; 13 | } 14 | 15 | export const useAddressHistoryStore = create()( 16 | persist( 17 | (set, get) => ({ 18 | addresses: [], 19 | addAddress: (address: string) => { 20 | return set({ 21 | addresses: [address, ...get().addresses.filter((a) => a !== address)].slice(0, 20), 22 | }); 23 | }, 24 | clear: () => set({ addresses: [] }), 25 | removeItem: (address: string) => { 26 | const { addresses } = get(); 27 | const newAddresses = addresses.filter((item) => item !== address); 28 | set({ addresses: newAddresses }); 29 | }, 30 | }), 31 | { 32 | name: "addressHistory", 33 | getStorage: () => localStorage, 34 | }, 35 | ), 36 | ); 37 | 38 | export function useAddressHistory() { 39 | const navigate = useNavigatePreserveQuery(); 40 | const { setValue, setActive } = useAddressInput(); 41 | const { addresses, addAddress, clear, removeItem } = useAddressHistoryStore(); 42 | const { contractAddress } = useContractAddress(); 43 | 44 | const onHistoryClear = useCallback(() => { 45 | clear(); 46 | }, [clear]); 47 | 48 | const onItemClick = useCallback((item: string) => { 49 | setValue(""); 50 | setActive(false); 51 | navigate(`/${item}`); 52 | }, []); 53 | 54 | const onItemDelete = useCallback( 55 | (e: React.MouseEvent, item: string) => { 56 | e.stopPropagation(); 57 | removeItem(item); 58 | }, 59 | [removeItem], 60 | ); 61 | 62 | useEffect(() => { 63 | if (contractAddress) { 64 | addAddress(contractAddress); 65 | } 66 | }, [contractAddress]); 67 | 68 | return { onHistoryClear, onItemClick, onItemDelete, addressHistory: addresses, addAddress }; 69 | } 70 | -------------------------------------------------------------------------------- /src/lib/useAddressInput.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from "react"; 2 | import { validateAddress } from "./useContractAddress"; 3 | import useNotification from "./useNotification"; 4 | import create from "zustand"; 5 | import { useNavigatePreserveQuery } from "./useNavigatePreserveQuery"; 6 | 7 | interface Props { 8 | value: string; 9 | active: boolean; 10 | setValue: (val: string) => void; 11 | setActive: (act: boolean) => void; 12 | } 13 | 14 | const useAddressStore = create((set) => ({ 15 | value: "", 16 | active: false, 17 | setValue: (val: string) => set({ value: val }), 18 | setActive: (act: boolean) => set({ active: act }), 19 | })); 20 | 21 | export function useAddressInput() { 22 | const { showNotification } = useNotification(); 23 | const navigate = useNavigatePreserveQuery(); 24 | const { value, setValue, active, setActive } = useAddressStore((state) => state); 25 | 26 | const onClear = useCallback(() => { 27 | setValue(""); 28 | }, []); 29 | 30 | const onSubmit = () => { 31 | let isAddressValid = validateAddress(value); 32 | if (!isAddressValid) { 33 | showNotification("Invalid address", "error"); 34 | return; 35 | } 36 | 37 | setValue(""); 38 | setActive(false); 39 | 40 | navigate(`/${value}`); 41 | }; 42 | 43 | return { 44 | onSubmit, 45 | onClear, 46 | setActive: setActive, 47 | setValue: setValue, 48 | active: active, 49 | value: value, 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /src/lib/useContractAddress.ts: -------------------------------------------------------------------------------- 1 | import { useParams } from "react-router-dom"; 2 | import { Address } from "ton"; 3 | import { useEffect } from "react"; 4 | import { useNavigatePreserveQuery } from "./useNavigatePreserveQuery"; 5 | 6 | function useContractAddress() { 7 | const navigate = useNavigatePreserveQuery(); 8 | const { contractAddress } = useParams(); 9 | const isAddressValid = validateAddress(contractAddress); 10 | const verifiedAddress = isAddressValid ? Address.parse(contractAddress!) : null; 11 | 12 | const verifiedAddressStr = verifiedAddress?.toString() ?? null; 13 | const verifiedAddresssHex = verifiedAddress?.toRawString() ?? null; 14 | 15 | useEffect(() => { 16 | if (contractAddress && verifiedAddress && verifiedAddressStr !== contractAddress) { 17 | navigate(`/${verifiedAddressStr}`, { replace: true }); 18 | } 19 | }, [contractAddress]); 20 | 21 | return { 22 | contractAddress: verifiedAddressStr, 23 | contractAddressHex: verifiedAddresssHex, 24 | isAddressEmpty: !contractAddress, 25 | }; 26 | } 27 | 28 | function validateAddress(contractAddress: string | undefined) { 29 | let isAddressValid = true; 30 | 31 | try { 32 | Address.parse(contractAddress ?? ""); 33 | } catch (e) { 34 | isAddressValid = false; 35 | } 36 | 37 | return isAddressValid; 38 | } 39 | 40 | export { useContractAddress, validateAddress }; 41 | -------------------------------------------------------------------------------- /src/lib/useCustomMutation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MutationKey, 3 | MutationFunction, 4 | UseMutationOptions, 5 | UseMutationResult, 6 | useQuery, 7 | useMutation, 8 | useIsMutating, 9 | useQueryClient, 10 | } from "@tanstack/react-query"; 11 | 12 | export const useCustomMutation = < 13 | TData = unknown, 14 | TError = unknown, 15 | TVariables = unknown, 16 | TContext = unknown, 17 | >( 18 | mutationKey: MutationKey, 19 | mutationFn: MutationFunction, 20 | options?: Omit< 21 | UseMutationOptions, 22 | "mutationKey" | "mutationFn" 23 | >, 24 | ): UseMutationResult & { 25 | invalidate: () => void; 26 | } => { 27 | const invalidate = () => { 28 | queryClient.invalidateQueries(["CustomMutation", mutationKey]); 29 | queryClient.invalidateQueries(["CustomMutationError", mutationKey]); 30 | }; 31 | 32 | const queryClient = useQueryClient(); 33 | const query = useQuery( 34 | ["CustomMutation", mutationKey], 35 | async () => await Promise.resolve(false as unknown as TData), 36 | { 37 | retry: false, 38 | cacheTime: Infinity, 39 | staleTime: Infinity, 40 | }, 41 | ); 42 | const queryError = useQuery( 43 | ["CustomMutationError", mutationKey], 44 | async () => await Promise.resolve(false as unknown as TError), 45 | { 46 | retry: false, 47 | cacheTime: Infinity, 48 | staleTime: Infinity, 49 | }, 50 | ); 51 | const mutation = useMutation( 52 | mutationKey, 53 | async (...params) => { 54 | // TODO maybe sometimes invalidation should be optional? 55 | invalidate(); 56 | 57 | queryClient.setQueryData(["CustomMutationError", mutationKey], false); 58 | return await mutationFn(...params); 59 | }, 60 | { 61 | ...options, 62 | onSuccess: (data, variables, context) => { 63 | queryClient.setQueryData(["CustomMutation", mutationKey], data); 64 | if (options?.onSuccess) options.onSuccess(data, variables, context); 65 | }, 66 | onError: (err, variables, context) => { 67 | queryClient.setQueryData(["CustomMutationError", mutationKey], err); 68 | if (options?.onError) options.onError(err, variables, context); 69 | }, 70 | }, 71 | ); 72 | const isLoading = useIsMutating(mutationKey); 73 | 74 | // We need typecasting here due the ADT about the mutation result, and as we're using a data not related to the mutation result 75 | // The typescript can't infer the type correctly. 76 | // eslint-disable-next-line @typescript-eslint/consistent-type-assertions 77 | return { 78 | ...mutation, 79 | data: query.data, 80 | isLoading: !!isLoading, 81 | error: queryError.data, 82 | isError: !!queryError.data, 83 | invalidate: invalidate, 84 | } as UseMutationResult & { 85 | invalidate: () => void; 86 | }; 87 | }; 88 | -------------------------------------------------------------------------------- /src/lib/useFileStore.tsx: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | 3 | import { immer } from "zustand/middleware/immer"; 4 | import { AnalyticsAction, sendAnalyticsEvent } from "./googleAnalytics"; 5 | 6 | export let acceptedFileExtensions = ["fc", "func", "pkg", "tolk"]; 7 | if (import.meta.env.VITE_ALLOW_FIFT) acceptedFileExtensions.push("fift"); 8 | 9 | export type FileToUpload = { 10 | fileObj: File; 11 | includeInCommand: boolean; 12 | hasIncludeDirectives: boolean; 13 | isEntrypoint: boolean; 14 | isStdlib: boolean; 15 | folder: string; 16 | }; 17 | 18 | type State = { 19 | files: FileToUpload[]; 20 | }; 21 | 22 | type DerivedState = { 23 | hasFiles: () => boolean; 24 | }; 25 | 26 | type Actions = { 27 | addFiles: (files: File[]) => void; 28 | setInclueInCommand: (name: string, include: boolean) => void; 29 | setDirectory: (name: string, folder: string) => void; 30 | removeFile: (name: string) => void; 31 | reorderFiles: (fileBeingReplaced: string, fileToReplaceWith: string) => void; 32 | reset: () => void; 33 | }; 34 | 35 | export const useFileStore = create( 36 | immer((set, get) => ({ 37 | // State 38 | files: [], 39 | 40 | // Derived 41 | hasFiles: () => get().files.length > 0, 42 | 43 | // Actions 44 | addFiles: async (files) => { 45 | const modifiedFiles = await Promise.all( 46 | files.map(async (f) => { 47 | const content = await f.text(); 48 | // @ts-ignore 49 | const folders = f.path?.split("/").filter((f) => f) ?? []; 50 | return { 51 | fileObj: f, 52 | includeInCommand: true, 53 | folder: folders.slice(0, folders.length - 1).join("/"), 54 | hasIncludeDirectives: content.includes("#include"), 55 | isEntrypoint: 56 | /\(\)\s*(recv_internal|recv_external|main)\s*\(/.test(content) || 57 | /fun (onInternalMessage|onExternalMessage)\s*\(/.test(content), 58 | isStdlib: /stdlib.(fc|func)/i.test(f.name), 59 | }; 60 | }), 61 | ); 62 | 63 | set((state) => { 64 | const filesToAdd = modifiedFiles.filter( 65 | (f) => 66 | f.fileObj.name.match(new RegExp(`.*\.(${acceptedFileExtensions.join("|")})$`)) && 67 | !state.files.find((existingF) => existingF.fileObj.name === f.fileObj.name), 68 | ); 69 | 70 | if (filesToAdd) { 71 | sendAnalyticsEvent(AnalyticsAction.ADD_FILE); 72 | state.files.push(...filesToAdd); 73 | } 74 | }); 75 | }, 76 | setInclueInCommand: (name: string, include: boolean) => { 77 | set((state) => { 78 | state.files.find((f) => f.fileObj.name === name)!.includeInCommand = include; 79 | }); 80 | }, 81 | setDirectory: (name: string, folder: string) => { 82 | set((state) => { 83 | state.files.find((f) => f.fileObj.name === name)!.folder = folder; 84 | }); 85 | }, 86 | removeFile: (name: string) => { 87 | set((state) => { 88 | state.files = state.files.filter((f) => f.fileObj.name !== name); 89 | }); 90 | }, 91 | reorderFiles: (fileBeingReplaced: string, fileToReplaceWith: string) => { 92 | set((state) => { 93 | const files = state.files; 94 | const oldIndex = files.findIndex((f) => f.fileObj.name === fileBeingReplaced); 95 | const newIndex = files.findIndex((f) => f.fileObj.name === fileToReplaceWith); 96 | const [removed] = files.splice(oldIndex, 1); 97 | files.splice(newIndex, 0, removed); 98 | }); 99 | }, 100 | reset: () => { 101 | set((state) => { 102 | state.files = []; 103 | }); 104 | }, 105 | })), 106 | ); 107 | -------------------------------------------------------------------------------- /src/lib/useHover.ts: -------------------------------------------------------------------------------- 1 | import { useState, useRef, useEffect } from "react"; 2 | 3 | export function useHover() { 4 | const [value, setValue] = useState(false); 5 | const ref = useRef(null); 6 | const handleMouseOver = () => setValue(true); 7 | const handleMouseOut = () => setValue(false); 8 | useEffect( 9 | () => { 10 | const node = ref.current as HTMLElement; 11 | if (node) { 12 | node.addEventListener("mouseover", handleMouseOver); 13 | node.addEventListener("mouseout", handleMouseOut); 14 | return () => { 15 | node.removeEventListener("mouseover", handleMouseOver); 16 | node.removeEventListener("mouseout", handleMouseOut); 17 | }; 18 | } 19 | }, 20 | [ref.current], // Recall only if ref changes 21 | ); 22 | return { hoverRef: ref, isHover: value }; 23 | } 24 | -------------------------------------------------------------------------------- /src/lib/useLoadContractInfo.ts: -------------------------------------------------------------------------------- 1 | import { Address, fromNano, Cell, CellType, BitReader, beginCell, BitString } from "ton"; 2 | import { useQuery } from "@tanstack/react-query"; 3 | 4 | import { fromCode } from "tvm-disassembler"; 5 | import { getClient } from "./getClient"; 6 | import { useContractAddress } from "./useContractAddress"; 7 | 8 | type CellHash = { 9 | base64: string; 10 | hex: string; 11 | }; 12 | 13 | export function tryLoadLibraryCodeCellHash(exoticCodeCell: Cell) { 14 | if (exoticCodeCell.isExotic && exoticCodeCell.type == CellType.Library) { 15 | const br = new BitReader(exoticCodeCell.bits); 16 | br.loadBits(8); 17 | return Buffer.from(br.loadBits(br.remaining).toString(), "hex"); 18 | } 19 | 20 | return null; 21 | } 22 | 23 | export function useLoadContractInfo() { 24 | const { contractAddress } = useContractAddress(); 25 | 26 | const { isLoading, error, data } = useQuery([contractAddress, "info"], async () => { 27 | if (!contractAddress) return null; 28 | const client = await getClient(); 29 | 30 | const _address = Address.parse(contractAddress); 31 | let { code, data } = await client.getContractState(_address); 32 | let codeCell = Cell.fromBoc(code!)[0]; 33 | let dataCell = Cell.fromBoc(data!)[0]; 34 | 35 | const b = await client.getBalance(_address); 36 | 37 | const libraryHash = tryLoadLibraryCodeCellHash(codeCell); 38 | 39 | let decompiled; 40 | 41 | if (libraryHash) { 42 | decompiled = "Library contract"; 43 | } else { 44 | try { 45 | decompiled = fromCode(codeCell); 46 | } catch (e) { 47 | decompiled = e?.toString(); 48 | } 49 | } 50 | 51 | const codeCellHash = codeCell.hash(); 52 | const dataCellHash = dataCell.hash(); 53 | 54 | return { 55 | codeCellHash: { 56 | base64: codeCellHash.toString("base64"), 57 | hex: codeCellHash.toString("hex"), 58 | } as CellHash, 59 | dataCellHash: { 60 | base64: dataCellHash.toString("base64"), 61 | hex: dataCellHash.toString("hex"), 62 | } as CellHash, 63 | decompiled, 64 | balance: fromNano(b), 65 | libraryHash: { 66 | base64: libraryHash?.toString("base64"), 67 | hex: libraryHash?.toString("hex"), 68 | }, 69 | codeCellToCompileBase64: (libraryHash ?? codeCellHash).toString("base64"), 70 | }; 71 | }); 72 | 73 | return { isLoading, error, data }; 74 | } 75 | -------------------------------------------------------------------------------- /src/lib/useLoadContractProof.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { Sha256 } from "@aws-crypto/sha256-js"; 3 | import { useLoadContractInfo } from "./useLoadContractInfo"; 4 | import "@ton-community/contract-verifier-sdk"; 5 | import { SourcesData } from "@ton-community/contract-verifier-sdk"; 6 | import { useContractAddress } from "./useContractAddress"; 7 | import { usePublishProof } from "./usePublishProof"; 8 | 9 | export const toSha256Buffer = (s: string) => { 10 | const sha = new Sha256(); 11 | sha.update(s); 12 | return Buffer.from(sha.digestSync()); 13 | }; 14 | 15 | export async function getProofIpfsLink(hash: string): Promise { 16 | return ContractVerifier.getSourcesJsonUrl(hash, { 17 | verifier: window.verifierId, 18 | testnet: window.isTestnet, 19 | }); 20 | } 21 | 22 | export function useLoadContractProof() { 23 | const { contractAddress } = useContractAddress(); 24 | const { data: contractInfo, error: contractError } = useLoadContractInfo(); 25 | const { status: publishProofStatus } = usePublishProof(); 26 | const { isLoading, error, data, refetch } = useQuery< 27 | Partial & { 28 | hasOnchainProof: boolean; 29 | } 30 | >( 31 | [contractAddress, "proof"], 32 | async () => { 33 | if (!contractAddress) { 34 | return { 35 | hasOnchainProof: false, 36 | }; 37 | } 38 | 39 | const ipfsLink = await getProofIpfsLink(contractInfo!.codeCellToCompileBase64); 40 | 41 | if (!ipfsLink) { 42 | return { hasOnchainProof: false, ipfsLink }; 43 | } 44 | 45 | const sourcesData = await ContractVerifier.getSourcesData(ipfsLink, { 46 | testnet: window.isTestnet, 47 | }); 48 | return { 49 | hasOnchainProof: true, 50 | ...sourcesData, 51 | }; 52 | }, 53 | { 54 | enabled: 55 | !!contractAddress && 56 | !!contractInfo?.codeCellToCompileBase64 && 57 | publishProofStatus === "initial", 58 | retry: 2, 59 | }, 60 | ); 61 | 62 | return { isLoading, error: error ?? contractError, data, refetch }; 63 | } 64 | -------------------------------------------------------------------------------- /src/lib/useLoadContractSourceCode.ts: -------------------------------------------------------------------------------- 1 | import { SourcesData } from "@ton-community/contract-verifier-sdk"; 2 | import { useLoadContractProof } from "./useLoadContractProof"; 3 | import { useEffect } from "react"; 4 | 5 | export function useLoadContractSourceCode() { 6 | const { data } = useLoadContractProof(); 7 | 8 | useEffect(() => { 9 | if (!data?.files) return; 10 | ContractVerifierUI.loadSourcesData(data as SourcesData, { 11 | containerSelector: "#myVerifierContainer", 12 | fileListSelector: "#myVerifierFiles", 13 | contentSelector: "#myVerifierContent", 14 | theme: "light", // TODO denis 15 | }); 16 | }, [data?.files]); 17 | 18 | return { 19 | hasOnchainProof: data?.hasOnchainProof, 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/useLoadLatestVerified.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | 3 | import { randomFromArray, backends } from "./useSubmitSources"; 4 | 5 | export function useLoadLatestVerified() { 6 | const backend = randomFromArray(backends); 7 | 8 | const { isLoading, error, data } = useQuery(["latestVerifiedContracts"], async () => { 9 | const response = await fetch(`${backend}/latestVerified`, { 10 | method: "GET", 11 | }); 12 | 13 | const latestVerified = ( 14 | (await response.json()) as { 15 | address: string; 16 | mainFile: string; 17 | compiler: string; 18 | }[] 19 | ).slice(0, 100); 20 | 21 | return latestVerified; 22 | }); 23 | 24 | return { isLoading, error, data }; 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/useLoadSourcesRegistryInfo.tsx: -------------------------------------------------------------------------------- 1 | import { getClient } from "./getClient"; 2 | import { Address, Cell } from "ton"; 3 | import { useQuery } from "@tanstack/react-query"; 4 | import { getAdmin } from "./getAdmin"; 5 | import { SourcesRegistry as SourcesRegistryContract } from "./wrappers/sources-registry"; 6 | 7 | export function useLoadSourcesRegistryInfo() { 8 | const address = Address.parse(window.sourcesRegistryAddress); 9 | return useQuery(["sourcesRegistry", address], async () => { 10 | const tc = await getClient(); 11 | const admin = await getAdmin(address, tc); 12 | const contract = tc.open(SourcesRegistryContract.createFromAddress(address)); 13 | 14 | const verifierRegistry = (await contract.getVerifierRegistryAddress()).toString(); 15 | const deploymentCosts = await contract.getDeploymentCosts(); 16 | 17 | const codeCellHash = Cell.fromBoc((await tc.getContractState(address)).code as Buffer)[0] 18 | .hash() 19 | .toString("base64"); 20 | return { 21 | admin, 22 | verifierRegistry, 23 | codeCellHash, 24 | address, 25 | deploymentCosts, 26 | }; 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /src/lib/useLoadVerifierRegistryInfo.tsx: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { Address } from "ton"; 3 | import { getClient } from "./getClient"; 4 | import { VerifierRegistry as VerifierRegistryContract } from "./wrappers/verifier-registry"; 5 | import { useLoadSourcesRegistryInfo } from "./useLoadSourcesRegistryInfo"; 6 | 7 | export function useLoadVerifierRegistryInfo() { 8 | const { data: sourceRegistryData } = useLoadSourcesRegistryInfo(); 9 | return useQuery( 10 | ["verifierRegistry", sourceRegistryData?.verifierRegistry], 11 | async () => { 12 | const tc = await getClient(); 13 | const contract = tc.open( 14 | VerifierRegistryContract.createFromAddress( 15 | Address.parse(sourceRegistryData!.verifierRegistry), 16 | ), 17 | ); 18 | const verifiers = await contract.getVerifiers(); 19 | return verifiers; 20 | }, 21 | { enabled: !!sourceRegistryData }, 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/lib/useNavigatePreserveQuery.ts: -------------------------------------------------------------------------------- 1 | import { NavigateOptions, To, useLocation, useNavigate } from "react-router-dom"; 2 | export function useNavigatePreserveQuery() { 3 | const location = useLocation(); 4 | const navigate = useNavigate(); 5 | 6 | return (to: To, options?: NavigateOptions) => { 7 | if (typeof to === "string") { 8 | navigate( 9 | { 10 | pathname: to, 11 | search: location.search, 12 | hash: location.hash, 13 | }, 14 | options, 15 | ); 16 | } else { 17 | navigate(to, options); 18 | } 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/lib/useNotification.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, useCallback } from "react"; 2 | import { VariantType, useSnackbar } from "notistack"; 3 | import { IconButton, styled } from "@mui/material"; 4 | import { Box } from "@mui/system"; 5 | import CloseIcon from "@mui/icons-material/Close"; 6 | 7 | const StyledMessage = styled(Box)({ 8 | "& &": { 9 | color: "white", 10 | }, 11 | "& a": { 12 | color: "white", 13 | }, 14 | }); 15 | 16 | function useNotification() { 17 | const { enqueueSnackbar, closeSnackbar } = useSnackbar(); 18 | 19 | const showNotification = useCallback( 20 | ( 21 | message: ReactNode | string, 22 | variant: VariantType, 23 | onClose?: () => void, 24 | autoHideDuration?: number, 25 | ) => { 26 | const key = enqueueSnackbar({message}, { 27 | variant, 28 | autoHideDuration: autoHideDuration || 5000, 29 | onClose, 30 | onClick: () => closeSnackbar(key), 31 | action: () => ( 32 | 33 | 34 | 35 | ), 36 | }); 37 | }, 38 | [closeSnackbar, enqueueSnackbar], 39 | ); 40 | 41 | return { showNotification }; 42 | } 43 | 44 | export default useNotification; 45 | -------------------------------------------------------------------------------- /src/lib/useOverride.ts: -------------------------------------------------------------------------------- 1 | import { useSearchParams } from "react-router-dom"; 2 | import { Address } from "ton"; 3 | import { getAdmin } from "./getAdmin"; 4 | import { getClient } from "./getClient"; 5 | import { useEffect, useState } from "react"; 6 | import { useContractAddress } from "./useContractAddress"; 7 | import { useTonAddress } from "@tonconnect/ui-react"; 8 | 9 | export function useOverride() { 10 | const { contractAddress } = useContractAddress(); 11 | const walletAddress = useTonAddress(); 12 | const [urlParams] = useSearchParams(); 13 | const [canOverride, setCanOverride] = useState(false); 14 | 15 | useEffect(() => { 16 | (async () => { 17 | if (!walletAddress || !contractAddress) return; 18 | if (urlParams.get("override") !== null) { 19 | const tc = await getClient(); 20 | const admin = await getAdmin(Address.parse(window.sourcesRegistryAddress), tc); 21 | if (admin === walletAddress) { 22 | setCanOverride(true); 23 | return; 24 | } 25 | } 26 | setCanOverride(false); 27 | })(); 28 | }, [walletAddress, contractAddress]); 29 | 30 | return canOverride; 31 | } 32 | -------------------------------------------------------------------------------- /src/lib/usePublishProof.ts: -------------------------------------------------------------------------------- 1 | import { Cell, Address, toNano } from "ton"; 2 | import { useSubmitSources } from "./useSubmitSources"; 3 | import { getProofIpfsLink } from "./useLoadContractProof"; 4 | import { useLoadContractInfo } from "./useLoadContractInfo"; 5 | import { useSendTXN } from "./useSendTxn"; 6 | import { AnalyticsAction, sendAnalyticsEvent } from "./googleAnalytics"; 7 | import { useEffect } from "react"; 8 | import { useLoadSourcesRegistryInfo } from "./useLoadSourcesRegistryInfo"; 9 | 10 | export function usePublishProof() { 11 | const { data: submitSourcesData } = useSubmitSources(); 12 | const { data: contractInfo } = useLoadContractInfo(); 13 | const { data: sourcesRegistryData } = useLoadSourcesRegistryInfo(); 14 | 15 | const { sendTXN, data, clearTXN } = useSendTXN("publishProof", async (count: number) => { 16 | const ipfsLink = await getProofIpfsLink(contractInfo!.codeCellToCompileBase64); 17 | 18 | if (count > 20) { 19 | return "error"; 20 | } 21 | 22 | return !!ipfsLink ? "success" : "issued"; 23 | }); 24 | 25 | useEffect(() => { 26 | switch (data.status) { 27 | case "pending": 28 | sendAnalyticsEvent(AnalyticsAction.PUBLISH_CLICK); 29 | break; 30 | case "issued": 31 | sendAnalyticsEvent(AnalyticsAction.TRANSACTION_ISSUED); 32 | break; 33 | case "rejected": 34 | sendAnalyticsEvent(AnalyticsAction.TRANSACTION_REJECTED); 35 | break; 36 | case "error": 37 | sendAnalyticsEvent(AnalyticsAction.TRANSACTION_ERROR); 38 | break; 39 | case "expired": 40 | sendAnalyticsEvent(AnalyticsAction.TRANSACTION_EXPIRED); 41 | break; 42 | case "success": 43 | sendAnalyticsEvent(AnalyticsAction.CONTRACT_DEPLOYED); 44 | break; 45 | } 46 | }, [data.status]); 47 | 48 | return { 49 | sendTXN: () => { 50 | sendTXN( 51 | Address.parse(sourcesRegistryData!.verifierRegistry), 52 | import.meta.env.DEV ? toNano("0.1") : toNano("0.5"), 53 | Cell.fromBoc(Buffer.from(submitSourcesData!.result.msgCell!))[0], 54 | ); 55 | }, 56 | status: data.status, 57 | clearTXN, 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /src/lib/usePublishSteps.ts: -------------------------------------------------------------------------------- 1 | import create from "zustand"; 2 | 3 | export const STEPS = { 4 | COMPILE: "COMPILE", 5 | PUBLISH: "PUBLISH", 6 | }; 7 | 8 | export const SECTIONS = { 9 | SOURCES: "SOURCES", 10 | PUBLISH: "PUBLISH", 11 | }; 12 | 13 | const DEFAULT = () => ({ 14 | step: STEPS.COMPILE, 15 | currentSection: SECTIONS.SOURCES, 16 | }); 17 | 18 | const publishSteps = (set: any) => ({ 19 | ...DEFAULT(), 20 | proceedToPublish: () => { 21 | set({ 22 | step: STEPS.PUBLISH, 23 | currentSection: SECTIONS.PUBLISH, 24 | }); 25 | }, 26 | toggleSection: (section: string) => { 27 | set({ 28 | currentSection: section, 29 | }); 30 | }, 31 | reset: () => { 32 | set(DEFAULT()); 33 | }, 34 | }); 35 | 36 | export const usePublishStore = create(publishSteps); 37 | -------------------------------------------------------------------------------- /src/lib/useRemoteConfig.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { useState } from "react"; 3 | 4 | const configURL = 5 | "https://raw.githubusercontent.com/ton-community/contract-verifier-config/main/config.json"; 6 | 7 | export function useRemoteConfig() { 8 | const [enabled, setEnabled] = useState(true); 9 | 10 | return useQuery( 11 | ["remoteConfig"], 12 | async () => { 13 | const { funcVersions, tactVersions, tolkVersions } = await (await fetch(configURL)).json(); 14 | 15 | setEnabled(false); 16 | 17 | return { 18 | funcVersions: funcVersions as string[], 19 | tactVersions: tactVersions as string[], 20 | tolkVersions: tolkVersions as string[], 21 | }; 22 | }, 23 | { enabled, initialData: { funcVersions: [], tactVersions: [], tolkVersions: [] } }, 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/useResetState.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useSubmitSources } from "./useSubmitSources"; 3 | import { useFileStore } from "./useFileStore"; 4 | import { useContractAddress } from "./useContractAddress"; 5 | import { usePublishStore } from "./usePublishSteps"; 6 | import { create } from "zustand"; 7 | 8 | // Adds support for preloading files from a different page, 9 | // to prevent resetting the file store when the address changes 10 | export const usePreload = create<{ 11 | isPreloaded: boolean; 12 | markPreloaded: () => void; 13 | clearPreloaded: () => void; 14 | }>((set) => ({ 15 | isPreloaded: false, 16 | markPreloaded: () => { 17 | set({ isPreloaded: true }); 18 | }, 19 | clearPreloaded: () => { 20 | set({ isPreloaded: false }); 21 | }, 22 | })); 23 | 24 | export function useResetState() { 25 | const { contractAddress } = useContractAddress(); 26 | const submitSourcesState = useSubmitSources(); 27 | const { reset: resetFileStore } = useFileStore(); 28 | const { reset: resetPublishStore } = usePublishStore(); 29 | const { isPreloaded, clearPreloaded } = usePreload(); 30 | 31 | useEffect(() => { 32 | if (!isPreloaded) { 33 | resetFileStore(); 34 | } else { 35 | // Clear for next address 36 | clearPreloaded(); 37 | } 38 | resetPublishStore(); 39 | submitSourcesState.invalidate(); 40 | }, [contractAddress]); 41 | } 42 | -------------------------------------------------------------------------------- /src/lib/useSendTxn.ts: -------------------------------------------------------------------------------- 1 | import { SendTransactionRequest, useTonConnectUI } from "@tonconnect/ui-react"; 2 | import { useEffect } from "react"; 3 | import { Address, Cell, StateInit } from "ton"; 4 | import create from "zustand"; 5 | import { useRequestTXN } from "../hooks"; 6 | 7 | export type TXNStatus = 8 | | "initial" 9 | | "pending" 10 | | "issued" 11 | | "rejected" 12 | | "error" 13 | | "expired" 14 | | "success"; 15 | 16 | const useTxnMonitors = create<{ 17 | txns: Record; 18 | updateTxn: (key: string, txn: TXNStatus) => void; 19 | }>((set, get) => ({ 20 | txns: {}, 21 | updateTxn: (key: string, txn: TXNStatus) => { 22 | set((state) => ({ txns: { ...get().txns, [key]: txn } })); 23 | }, 24 | })); 25 | 26 | /* 27 | TODOs 28 | 1. Support a broader API - tonkeeper / ton-connection 29 | 2. Ensure connection exists 30 | */ 31 | 32 | export function useSendTXN(key: string, monitorSuccess: (count: number) => Promise) { 33 | const requestTXN = useRequestTXN(); 34 | const { updateTxn, txns } = useTxnMonitors(); 35 | 36 | useEffect(() => { 37 | if (!txns[key]) { 38 | updateTxn(key, "initial"); 39 | } 40 | }, []); 41 | 42 | return { 43 | sendTXN: async (to: Address, value: bigint, message?: Cell, stateInit?: StateInit) => { 44 | updateTxn(key, "pending"); 45 | const status = await requestTXN(to.toString(), value, message, stateInit); 46 | 47 | let i = 1; 48 | 49 | if (status === "issued") { 50 | updateTxn(key, "issued"); 51 | const _id = setInterval(async () => { 52 | const txnStatus = await monitorSuccess(i); 53 | i++; 54 | updateTxn(key, txnStatus); 55 | if (txnStatus !== "issued") { 56 | clearInterval(_id); 57 | } 58 | }, 2000); 59 | } else if (status === "rejected") { 60 | updateTxn(key, "rejected"); 61 | } 62 | }, 63 | data: { status: txns[key] }, 64 | clearTXN: () => { 65 | updateTxn(key, "initial"); 66 | }, 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /src/lib/workchainForAddress.ts: -------------------------------------------------------------------------------- 1 | import { Address } from "ton"; 2 | 3 | export function workchainForAddress(address: string): string { 4 | try { 5 | const _address = Address.parse(address); 6 | switch (_address.workChain) { 7 | case -1: 8 | return "Masterchain (-1)"; 9 | case 0: 10 | return "Basic Workchain (0)"; 11 | default: 12 | return `${_address.workChain}`; 13 | } 14 | } catch (e) { 15 | return ""; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/wrappers/sources-registry.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Address, 3 | beginCell, 4 | Cell, 5 | Contract, 6 | ContractProvider, 7 | fromNano, 8 | Sender, 9 | SendMode, 10 | } from "ton-core"; 11 | 12 | import { toBigIntBE } from "bigint-buffer"; 13 | import { Sha256 } from "@aws-crypto/sha256-js"; 14 | 15 | export const toSha256Buffer = (s: string) => { 16 | const sha = new Sha256(); 17 | sha.update(s); 18 | return Buffer.from(sha.digestSync()); 19 | }; 20 | 21 | export class SourcesRegistry implements Contract { 22 | constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {} 23 | 24 | static createFromAddress(address: Address) { 25 | return new SourcesRegistry(address); 26 | } 27 | 28 | async sendInternalMessage(provider: ContractProvider, via: Sender, body: Cell, value: bigint) { 29 | await provider.internal(via, { 30 | value: value, 31 | sendMode: SendMode.PAY_GAS_SEPARATELY, 32 | body: body, 33 | }); 34 | } 35 | 36 | async sendDeploy(provider: ContractProvider, via: Sender, value: bigint, bounce = true) { 37 | await provider.internal(via, { 38 | value, 39 | sendMode: SendMode.PAY_GAS_SEPARATELY, 40 | body: beginCell().endCell(), 41 | bounce, 42 | }); 43 | } 44 | 45 | async getChildAddressFromChain( 46 | provider: ContractProvider, 47 | verifier: string, 48 | codeCellHash: string, 49 | ): Promise
{ 50 | const result = await provider.get("get_source_item_address", [ 51 | { 52 | type: "int", 53 | value: toBigIntBE(toSha256Buffer(verifier)), 54 | }, 55 | { 56 | type: "int", 57 | value: toBigIntBE(Buffer.from(codeCellHash, "base64")), 58 | }, 59 | ]); 60 | const item = result.stack.readCell(); 61 | 62 | return item.beginParse().loadAddress()!; 63 | } 64 | 65 | async getVerifierRegistryAddress(provider: ContractProvider): Promise
{ 66 | const res = await provider.get("get_verifier_registry_address", []); 67 | const item = res.stack.readCell(); 68 | return item.beginParse().loadAddress(); 69 | } 70 | 71 | async getAdminAddress(provider: ContractProvider) { 72 | const res = await provider.get("get_admin_address", []); 73 | const item = res.stack.readCell(); 74 | return item.beginParse().loadMaybeAddress(); 75 | } 76 | 77 | async getCodeOpt(provider: ContractProvider) { 78 | const state = await provider.getState(); 79 | if (state.state.type != "active") return null; 80 | return state.state.code; 81 | } 82 | 83 | async getDeploymentCosts(provider: ContractProvider) { 84 | const res = await provider.get("get_deployment_costs", []); 85 | const min = res.stack.readBigNumber(); 86 | const max = res.stack.readBigNumber(); 87 | return { min: fromNano(min), max: fromNano(max) }; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/lib/wrappers/verifier-registry.ts: -------------------------------------------------------------------------------- 1 | import { toBufferBE } from "bigint-buffer"; 2 | import { 3 | Address, 4 | beginCell, 5 | Cell, 6 | Contract, 7 | ContractProvider, 8 | Sender, 9 | SendMode, 10 | Dictionary, 11 | DictionaryValue, 12 | Slice, 13 | } from "ton-core"; 14 | 15 | export type Verifier = { 16 | admin: Address; 17 | quorum: number; 18 | pubKeyEndpoints: Record; 19 | name: string; 20 | url: string; 21 | }; 22 | 23 | export const OperationCodes = { 24 | removeVerifier: 0x19fa5637, 25 | updateVerifier: 0x6002d61a, 26 | forwardMessage: 0x75217758, 27 | }; 28 | 29 | export type CollectionMintItemInput = { 30 | passAmount: bigint; 31 | index: number; 32 | ownerAddress: Address; 33 | content: string; 34 | }; 35 | 36 | function num2ip(num: bigint) { 37 | let d = toBufferBE(num, 4); 38 | return [d[0].toString(), d[1].toString(), d[2].toString(), d[3].toString()].join("."); 39 | } 40 | 41 | function createSliceValue(): DictionaryValue { 42 | return { 43 | serialize: (src, buidler) => { 44 | buidler.storeSlice(src); 45 | }, 46 | parse: (src) => { 47 | return src; 48 | }, 49 | }; 50 | } 51 | 52 | export class VerifierRegistry implements Contract { 53 | constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {} 54 | 55 | static createFromAddress(address: Address) { 56 | return new VerifierRegistry(address); 57 | } 58 | 59 | async sendInternalMessage(provider: ContractProvider, via: Sender, body: Cell, value: bigint) { 60 | await provider.internal(via, { 61 | value: value, 62 | sendMode: SendMode.PAY_GAS_SEPARATELY, 63 | body: body, 64 | }); 65 | } 66 | 67 | async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) { 68 | await provider.internal(via, { 69 | value, 70 | sendMode: SendMode.PAY_GAS_SEPARATELY, 71 | body: beginCell().endCell(), 72 | }); 73 | } 74 | 75 | async getVerifier( 76 | provider: ContractProvider, 77 | id: bigint, 78 | ): Promise<{ admin: Address | null; settings: Cell | null }> { 79 | let res = await provider.get("get_verifier", [ 80 | { 81 | type: "int", 82 | value: id, 83 | }, 84 | ]); 85 | const sl = res.stack.readCell(); 86 | const settings = res.stack.readCellOpt(); 87 | const ok = res.stack.readNumber(); 88 | if (ok == 0) { 89 | return { 90 | admin: null, 91 | settings: null, 92 | }; 93 | } 94 | 95 | return { 96 | admin: sl.beginParse().loadAddress(), 97 | settings, 98 | }; 99 | } 100 | 101 | async getVerifiersNum(provider: ContractProvider): Promise { 102 | let res = await provider.get("get_verifiers_num", []); 103 | let num = res.stack.readNumber(); 104 | 105 | return num; 106 | } 107 | 108 | async getVerifiers(provider: ContractProvider): Promise { 109 | let res = await provider.get("get_verifiers", []); 110 | const item = res.stack.readCell(); 111 | const c = item.beginParse(); 112 | const d = c.loadDict(Dictionary.Keys.BigUint(256), createSliceValue()); 113 | 114 | return Array.from(d.values()).map((v) => { 115 | const admin = v.loadAddress()!; 116 | const quorom = v.loadUint(8); 117 | const pubKeyEndpoints = v.loadDict( 118 | Dictionary.Keys.BigUint(256), 119 | Dictionary.Values.BigUint(32), 120 | ); 121 | 122 | return { 123 | admin: admin, 124 | quorum: quorom, 125 | pubKeyEndpoints: Object.fromEntries( 126 | Array.from(pubKeyEndpoints).map(([k, v]) => { 127 | return [toBufferBE(k, 32).toString("base64"), num2ip(v)]; 128 | }), 129 | ), 130 | name: v.loadRef().beginParse().loadStringTail(), 131 | url: v.loadRef().beginParse().loadStringTail(), 132 | }; 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 2 | import React from "react"; 3 | import ReactDOM from "react-dom/client"; 4 | import { BrowserRouter, Route, Routes } from "react-router-dom"; 5 | import App from "./App"; 6 | import ContractInteract from "./components/admin/ContractInteract"; 7 | import "./index.css"; 8 | import { SnackbarProvider } from "notistack"; 9 | import { ThemeProvider } from "@mui/material"; 10 | import { theme } from "./theme"; 11 | import { Admin } from "./components/admin/Admin"; 12 | import { initGA } from "./lib/googleAnalytics"; 13 | import { TactDeployer } from "./components/tactDeployer/TactDeployer"; 14 | import { TonConnectUIProvider } from "@tonconnect/ui-react"; 15 | 16 | const queryClient = new QueryClient({ 17 | defaultOptions: { queries: { refetchOnWindowFocus: false } }, 18 | }); 19 | 20 | initGA(); 21 | 22 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 23 | 24 | 25 | 26 | 27 | 28 | 29 | } /> 30 | } /> 31 | } /> 32 | } /> 33 | } /> 34 | 35 | 36 | 37 | 38 | 39 | , 40 | ); 41 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | import { Buffer } from "buffer"; 2 | globalThis.Buffer = Buffer; 3 | -------------------------------------------------------------------------------- /src/styles.ts: -------------------------------------------------------------------------------- 1 | import { styled } from "@mui/material"; 2 | import { TonConnectButton } from "@tonconnect/ui-react"; 3 | 4 | export const StyledTonConnectButton = styled(TonConnectButton)(({ theme }) => ({ 5 | button: { 6 | background: theme.palette.primary.main, 7 | "*": { color: "white" }, 8 | svg: { 9 | "*": { 10 | stroke: "white", 11 | }, 12 | }, 13 | }, 14 | })); 15 | -------------------------------------------------------------------------------- /src/theme.ts: -------------------------------------------------------------------------------- 1 | import { createTheme } from "@mui/material"; 2 | 3 | export const theme = createTheme({ 4 | typography: { 5 | fontFamily: "Mulish", 6 | }, 7 | components: { 8 | MuiSwitch: { 9 | styleOverrides: { 10 | switchBase: { 11 | // Controls default (unchecked) color for the thumb 12 | color: "#ccc", 13 | }, 14 | colorPrimary: { 15 | "&.Mui-checked": { 16 | // Controls checked color for the thumb 17 | color: "#fff", 18 | }, 19 | }, 20 | track: { 21 | // Controls default (unchecked) color for the track 22 | opacity: 1, 23 | backgroundColor: "#D1D1D6", 24 | ".Mui-checked.Mui-checked + &": { 25 | // Controls checked color for the track 26 | opacity: 1, 27 | backgroundColor: "#0088CC", 28 | }, 29 | }, 30 | thumb: { 31 | background: "#fff", 32 | boxShadow: "0px 2px 8px rgba(0, 0, 0, 0.16)", 33 | }, 34 | }, 35 | }, 36 | }, 37 | }); 38 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { Address, beginCell } from "ton"; 2 | 3 | export const isValidAddress = (address: string, errorText?: string) => { 4 | try { 5 | const result = Address.parse(address); 6 | if (result && result.equals(zeroAddress())) { 7 | return false; 8 | } 9 | return true; 10 | } catch (error) { 11 | return false; 12 | } 13 | }; 14 | 15 | export function zeroAddress(): Address { 16 | return beginCell() 17 | .storeUint(2, 2) 18 | .storeUint(0, 1) 19 | .storeUint(0, 8) 20 | .storeUint(0, 256) 21 | .endCell() 22 | .beginParse() 23 | .loadAddress(); 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/generalUtils.ts: -------------------------------------------------------------------------------- 1 | export const isWebAssemblySupported = () => { 2 | return (() => { 3 | try { 4 | if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") { 5 | const module = new WebAssembly.Module( 6 | Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00), 7 | ); 8 | if (module instanceof WebAssembly.Module) 9 | return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; 10 | } 11 | } catch (e) {} 12 | return false; 13 | })(); 14 | }; 15 | 16 | export const isOnLocalHost = () => 17 | window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1"; 18 | -------------------------------------------------------------------------------- /src/utils/jsonUtils.ts: -------------------------------------------------------------------------------- 1 | export function download(content: string, fileName: string, contentType: string) { 2 | const a = document.createElement("a"); 3 | const file = new Blob([content], { type: contentType }); 4 | a.href = URL.createObjectURL(file); 5 | a.download = fileName; 6 | a.click(); 7 | } 8 | 9 | async function downloadJson(link: string) { 10 | try { 11 | let response = await fetch(link); 12 | let responseJson = await response.json(); 13 | download(JSON.stringify(responseJson), "sources.json", "text/json"); 14 | } catch (error) { 15 | console.error(error); 16 | } 17 | } 18 | 19 | export { downloadJson }; 20 | -------------------------------------------------------------------------------- /src/utils/linkUtils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FiftVersion, 3 | FuncCompilerVersion, 4 | TactVersion, 5 | TolkVersion 6 | } from "@ton-community/contract-verifier-sdk"; 7 | 8 | export const funcVersionToLink = (version: FuncCompilerVersion) => 9 | `https://github.com/ton-blockchain/ton/tree/func-${version}/crypto/func`; 10 | 11 | // Fift is tied to a FunC version 12 | export const fiftVersionToLink = (version: FiftVersion) => 13 | `https://github.com/ton-blockchain/ton/tree/func-${version}/crypto/fift`; 14 | 15 | export const tactVersionToLink = (version: TactVersion) => 16 | `https://github.com/tact-lang/tact/tree/v${version}`; 17 | 18 | export const tolkVersionToLink = (version: TolkVersion) => 19 | `https://github.com/ton-blockchain/ton/tree/tolk-${version}` 20 | 21 | export const dropPatchVersionZero = (version: string) => { 22 | const parsed = version.split('.'); 23 | const chunksCount = parsed.length; 24 | 25 | if(chunksCount == 0) { 26 | return version; 27 | } 28 | 29 | return Number(parsed[chunksCount - 1]) == 0 ? parsed.slice(0, chunksCount - 1).join('.') : version; 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/numberUtils.ts: -------------------------------------------------------------------------------- 1 | export const formatBalance = new Intl.NumberFormat("en-US", { 2 | minimumFractionDigits: 4, 3 | }); 4 | -------------------------------------------------------------------------------- /src/utils/textUtils.ts: -------------------------------------------------------------------------------- 1 | const makeElipsisAddress = (address?: string | null, padding?: number): string => { 2 | const paddingValue = padding || 10; 3 | if (!address) return ""; 4 | const firstPart = address.substring(0, paddingValue); 5 | const secondPart = address.substring(address.length - paddingValue); 6 | return `${firstPart}...${secondPart}`; 7 | }; 8 | 9 | const trimDirectory = (str: string): string => 10 | str 11 | .replace(/\/+/g, "/") 12 | .replace(/^\/[^\/]/, "") 13 | .replace(/\/$/, ""); 14 | 15 | export { makeElipsisAddress, trimDirectory }; 16 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | build: { 8 | // sourcemap: true, 9 | target: ["es2020"], 10 | }, 11 | optimizeDeps: { 12 | esbuildOptions: { 13 | target: "es2020", 14 | }, 15 | }, 16 | }); 17 | --------------------------------------------------------------------------------