├── .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 |
5 |
--------------------------------------------------------------------------------
/src/assets/compiler.svg:
--------------------------------------------------------------------------------
1 |
20 |
--------------------------------------------------------------------------------
/src/assets/contract.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/src/assets/copy.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/delete.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/dnd.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/download.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/assets/drag-handle.svg:
--------------------------------------------------------------------------------
1 |
27 |
--------------------------------------------------------------------------------
/src/assets/file.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/folder.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/github-dark.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/github-footer.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/github-hover.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/github.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/heart.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/info.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/assets/light-bulb.svg:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/src/assets/like.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/src/assets/qr.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/recent-search.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/search.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/show.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/sources.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/src/assets/telegram-hover.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/telegram.svg:
--------------------------------------------------------------------------------
1 |
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 |
4 |
--------------------------------------------------------------------------------
/src/assets/upload.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/verification-bomb.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/assets/verification.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/src/assets/verified.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/verify_icon_2.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
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 |
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 |
63 |
64 | Add sources
65 |
66 | {hasFiles() && step !== STEPS.PUBLISH && (
67 |
68 |
75 |
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 |
45 | TON VERIFIER
46 |
47 |
48 |
49 |
54 |
59 |
60 |
61 |
62 |
63 |
64 | © 2023
65 |
66 |
67 |
68 | Contributed with
69 |
70 |
71 |
72 | by
73 |
74 |
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 |
44 | GitHub
45 |
46 |
47 | navigate("/")}>
48 |
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 |
28 |
29 | {result}
30 |
31 | onItemDelete(e, result)}>
32 |
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 |
54 | TON VERIFIER
55 |
56 |
57 |
58 |
59 |
60 |
61 |
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 |
26 |
27 |
28 |
29 |
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 |
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 |
37 | TACT DEPLOYER
38 |
39 |
40 |
41 |
42 |
43 | {!isSmallScreen && (
44 |
45 |
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 |
--------------------------------------------------------------------------------