├── .github └── workflows │ ├── docker-publish.yml │ └── node.js.yml ├── .gitignore ├── Dockerfile ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json ├── nervos-logo.png └── robots.txt ├── server.js ├── src ├── api │ └── blockchain.ts ├── components │ ├── home │ │ ├── Home.css │ │ ├── Home.tsx │ │ └── logo.svg │ ├── toolbox │ │ ├── FloatingBox.tsx │ │ ├── style.css │ │ └── tools │ │ │ ├── hex2decimal.tsx │ │ │ ├── queryCell.tsx │ │ │ └── queryTx.tsx │ ├── tutorial │ │ ├── Learn.tsx │ │ └── sections │ │ │ ├── BeforeWeGetStarted.tsx │ │ │ ├── Class1.tsx │ │ │ ├── Class2.tsx │ │ │ ├── Class3.tsx │ │ │ ├── Class4.tsx │ │ │ ├── PreKnowledge.tsx │ │ │ ├── ShowChainInfo.tsx │ │ │ ├── class_1 │ │ │ ├── CompleteTxWithWitness.tsx │ │ │ ├── FetchToSignMessage.tsx │ │ │ ├── SendTx.tsx │ │ │ ├── SerializedWitnessArgs.tsx │ │ │ ├── Signer.tsx │ │ │ └── ToTxHash.tsx │ │ │ ├── common │ │ │ ├── Block.tsx │ │ │ ├── Blocks.tsx │ │ │ ├── CapacityOfCell.tsx │ │ │ ├── Cell.tsx │ │ │ ├── Cells.tsx │ │ │ ├── DragCellToInput.tsx │ │ │ ├── DragCellToInputBall.tsx │ │ │ ├── DragCellToInputJson.tsx │ │ │ ├── EditOutputCells.tsx │ │ │ ├── ItemTypes.ts │ │ │ ├── OutputCreator.tsx │ │ │ ├── TotalCapacity.tsx │ │ │ ├── Tx.tsx │ │ │ ├── TxConstructor.tsx │ │ │ ├── Txs.tsx │ │ │ ├── Wallets.tsx │ │ │ └── callBacks.ts │ │ │ └── show_chain_info │ │ │ ├── Balance.tsx │ │ │ ├── ChainConfig.tsx │ │ │ ├── WalletCells.tsx │ │ │ ├── WalletTransaction.tsx │ │ │ └── new_blocks.tsx │ └── widget │ │ ├── alert_messager.tsx │ │ ├── button.css │ │ ├── code.tsx │ │ ├── common_style.ts │ │ ├── copy_text.tsx │ │ ├── data_grid.tsx │ │ ├── feedback │ │ └── feedback.tsx │ │ ├── fireworks │ │ ├── animate.js │ │ ├── firework.tsx │ │ └── style.css │ │ ├── floating_cell │ │ ├── Fcell.jsx │ │ ├── cell_concept.jsx │ │ ├── space-bg.jpg │ │ └── style.css │ │ ├── form.tsx │ │ ├── fresh_button.tsx │ │ ├── i18nSwitcher.tsx │ │ ├── logo_svg.tsx │ │ ├── neon_text.css │ │ ├── neon_text.tsx │ │ ├── notify.tsx │ │ ├── table_of_contents.css │ │ └── table_of_contents.tsx ├── config │ ├── constant.json │ └── table_of_contents.json ├── i18n.ts ├── index.css ├── index.tsx ├── locales │ ├── en.json │ ├── es.json │ └── zh.json ├── react-app-env.d.ts ├── react-i18next.d.ts ├── reportWebVitals.ts ├── resource │ ├── lock-box.png │ ├── nervos-token-logo-white.jpg │ └── open-box.png ├── router.tsx ├── setupTests.ts ├── types │ ├── blockchain.ts │ └── i18n.ts └── utils │ ├── ckb-validator.js │ ├── index.ts │ └── validator.ts ├── tsconfig.json └── yarn.lock /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | on: 4 | push: 5 | branches: [develop] 6 | # Publish semver tags as releases. 7 | tags: ["v*.*.*"] 8 | pull_request: 9 | branches: [develop] 10 | 11 | env: 12 | # Use docker.io for Docker Hub if empty 13 | REGISTRY: 'ghcr.io/' 14 | # github.repository as / 15 | IMAGE_NAME: zero2ckb-web-prebuilds 16 | 17 | jobs: 18 | docker-build-push: 19 | runs-on: ubuntu-latest 20 | permissions: 21 | contents: read 22 | packages: write 23 | 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@v2 27 | 28 | # Login against a Docker registry except on PR 29 | # https://github.com/docker/login-action 30 | # The GHP_CRT secret is password or personal access token with `write:packages` access scope 31 | - name: Log into registry ${{ env.REGISTRY }} 32 | if: github.event_name != 'pull_request' 33 | uses: docker/login-action@v1 34 | with: 35 | registry: ${{ env.REGISTRY }} 36 | username: ${{ github.actor }} 37 | password: ${{ secrets.GHP_CRT }} 38 | 39 | # Extract metadata (tags, labels) for Docker 40 | # https://github.com/docker/metadata-action 41 | - name: Extract Docker metadata 42 | id: meta 43 | uses: docker/metadata-action@v3 44 | with: 45 | images: ${{ env.REGISTRY }}${{ github.repository_owner }}/${{ env.IMAGE_NAME }} 46 | 47 | - name: Get current release commit id 48 | id: commit 49 | run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" 50 | 51 | # Build and push Docker image with Buildx (don't push on PR) 52 | # https://github.com/docker/build-push-action 53 | - name: normal commit => Build and push Docker image to ${{ env.REGISTRY }}${{ github.repository_owner }}/${{ env.IMAGE_NAME }} 54 | if: github.ref_type != 'tag' 55 | uses: docker/build-push-action@v2 56 | with: 57 | context: . 58 | push: ${{ github.event_name != 'pull_request' }} 59 | tags: ${{ steps.meta.outputs.tags }}-${{ steps.commit.outputs.sha_short }} 60 | labels: ${{ steps.meta.outputs.labels }} 61 | 62 | # Build and push Docker image with Buildx (don't push on PR) 63 | # only for new tag 64 | - name: official tag release => Build and push Docker image to ${{ env.REGISTRY }}${{ github.repository_owner }}/${{ env.IMAGE_NAME }} 65 | if: ${{ startsWith(github.ref, 'refs/tags') }} 66 | uses: docker/build-push-action@v2 67 | with: 68 | context: . 69 | push: ${{ github.event_name != 'pull_request' }} 70 | tags: ${{ steps.meta.outputs.tags }} 71 | labels: ${{ steps.meta.outputs.labels }} 72 | 73 | - name: Setup tmate session 74 | if: ${{ failure() }} 75 | uses: mxschmitt/action-tmate@v3 76 | timeout-minutes: 30 77 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: 9 | - develop 10 | pull_request: 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [14.x, 16.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v2 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'yarn' 29 | - run: yarn install 30 | - run: yarn run build 31 | env: 32 | CI: false 33 | - run: yarn run fmt 34 | - run: git diff --exit-code 35 | -------------------------------------------------------------------------------- /.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 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .eslintcache 26 | .vscode 27 | .env 28 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14-bullseye 2 | 3 | COPY . /zero2ckb-web/. 4 | RUN cd /zero2ckb-web && yarn && yarn build 5 | 6 | RUN npm install pm2 -g 7 | 8 | RUN echo "Finished installing dependencies" 9 | 10 | EXPOSE 3000 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CKBee Onboarding Guide 2 | 3 | for a lot of CKBee, meaning new CKB developers / CKB newbies / or just some new people who get interested at CKB, learning CKB and understanding how CKB works sometimes can be a lot pain since the design of CKB is so different from other blockchains and CKB is so flexible for developers to coding and building apps on top of. 4 | 5 | in order to lower the threshold, we create [Zero2CKB](https://zero2ckb.ckbapp.dev/), a place to learn CKB from scratch, using no code and no software, just plain web page! 6 | 7 | the onboarding guide contains with 4 courses, at the end of this tutorial, you will complete the following task: 8 | 9 | - [x] send a pkp2h transaction 10 | - [ ] send a multisig transaction 11 | - [ ] deploy a simple contract 12 | - [ ] deploy a upgradable contract 13 | 14 | the website now is under heavy developing and considered to be not finished yet. if you got any question or any suggestion, you can just open a issue or even an PR in this repo. 15 | 16 | ## How to run 17 | 18 | change the `src/config/constant.json` server url if needed. 19 | 20 | ```sh 21 | yarn && yarn build 22 | PORT= yarn deploy 23 | ``` 24 | 25 | ## How to publish 26 | 1. Fork repositories 27 | 2. Change the code 28 | 3. Run `yarn fmt` 29 | 4. Push code 30 | 5. Create pull request 31 | 32 | ## prebuild images 33 | 34 | the repo's [github packages](https://github.com/RetricSu/zero2ckb-web/pkgs/container/zero2ckb-web-prebuilds) will deploy prebuild image. the entire project is copy into `/zero2ck-web` folder in the image, with `node_module` pre-installed and react UI client pre-build. 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zero2ckb-web", 3 | "version": "0.1.0", 4 | "private": true, 5 | "author": "retricsu ", 6 | "dependencies": { 7 | "@material-ui/core": "^4.11.2", 8 | "@testing-library/jest-dom": "^5.11.4", 9 | "@testing-library/react": "^11.1.0", 10 | "@testing-library/user-event": "^12.1.10", 11 | "@types/jest": "^26.0.15", 12 | "@types/node": "^12.0.0", 13 | "@types/react": "^16.9.53", 14 | "@types/react-dom": "^17.0.0", 15 | "@types/react-router-dom": "^5.1.6", 16 | "@types/react-select": "^3.0.27", 17 | "axios": "^0.21.0", 18 | "express": "^4.17.1", 19 | "i18next": "^21.6.13", 20 | "i18next-browser-languagedetector": "^6.1.3", 21 | "jsbi": "^3.1.4", 22 | "prettier": "^2.5.1", 23 | "rc-tween-one": "^2.7.3", 24 | "react": "^17.0.1", 25 | "react-dnd": "^11.1.3", 26 | "react-dnd-html5-backend": "^11.1.3", 27 | "react-dom": "^17.0.1", 28 | "react-github-btn": "^1.2.2", 29 | "react-i18next": "^11.15.5", 30 | "react-router-dom": "^5.2.0", 31 | "react-scripts": "4.0.1", 32 | "react-select": "^3.1.1", 33 | "react-spring": "^8.0.27", 34 | "react-toastify": "^6.1.0", 35 | "typescript": "^4.1", 36 | "web-vitals": "^0.2.4" 37 | }, 38 | "scripts": { 39 | "fmt": "prettier --write tsconfig.json server.js src/*.{ts,tsx,css} src/**/*.{ts,tsx,js,jsx,css,json} package.json", 40 | "start": "react-scripts start", 41 | "build": "react-scripts build", 42 | "test": "react-scripts test", 43 | "eject": "react-scripts eject", 44 | "serve": "serve -s build", 45 | "static-deploy": "yarn build && pm2 serve build 9111 --spa", 46 | "deploy": "node server.js" 47 | }, 48 | "eslintConfig": { 49 | "extends": [ 50 | "react-app", 51 | "react-app/jest" 52 | ] 53 | }, 54 | "browserslist": { 55 | "production": [ 56 | ">0.2%", 57 | "not dead", 58 | "not op_mini all" 59 | ], 60 | "development": [ 61 | "last 1 chrome version", 62 | "last 1 firefox version", 63 | "last 1 safari version" 64 | ] 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetricSu/zero2ckb-web/05d8f47226c8b489e7d2a535622f08f179e7c548/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Learn CKB From 0 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetricSu/zero2ckb-web/05d8f47226c8b489e7d2a535622f08f179e7c548/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetricSu/zero2ckb-web/05d8f47226c8b489e7d2a535622f08f179e7c548/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/nervos-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetricSu/zero2ckb-web/05d8f47226c8b489e7d2a535622f08f179e7c548/public/nervos-logo.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const path = require("path"); 3 | const app = express(); 4 | 5 | app.use(express.static(path.join(__dirname, "build"))); 6 | 7 | app.get("/*", function (req, res) { 8 | res.sendFile(path.join(__dirname, "build", "index.html")); 9 | }); 10 | 11 | app.listen(process.env.PORT || 3000); 12 | -------------------------------------------------------------------------------- /src/api/blockchain.ts: -------------------------------------------------------------------------------- 1 | import axios, { Method } from "axios"; 2 | import config from "../config/constant.json"; 3 | import utils from "../utils/index"; 4 | import type { 5 | Transaction, 6 | QueryOption, 7 | RawTransaction, 8 | WitnessArgs, 9 | HexString, 10 | Cell, 11 | Block, 12 | Wallet, 13 | HexNumber, 14 | ChainConfig, 15 | Hash, 16 | Message, 17 | } from "../types/blockchain"; 18 | 19 | //axios.defaults.withCredentials = true; 20 | 21 | export enum ApiRequestResponseStatus { 22 | ok = "ok", 23 | failed = "failed", 24 | } 25 | export interface ApiRequestResponse { 26 | status: ApiRequestResponseStatus; 27 | data?: any; 28 | error?: string; 29 | } 30 | 31 | export enum ApiRequestType { 32 | get, 33 | post, 34 | } 35 | 36 | class Api { 37 | base_url: string; 38 | 39 | constructor() { 40 | this.base_url = 41 | utils.get_env_mode() === "development" 42 | ? config.development_server_url 43 | : config.production_server_url; 44 | } 45 | 46 | private async httpRequest( 47 | target: string, 48 | params: any = {}, 49 | type: ApiRequestType = ApiRequestType.get 50 | ): Promise { 51 | const callRequest = async (method: Method) => { 52 | const url = `${this.base_url}/${target}`; 53 | const res = await axios({ url, method, params }); 54 | const response = res.data as ApiRequestResponse; 55 | if (response.status === ApiRequestResponseStatus.failed) { 56 | throw new Error("[ApiRequestError]: " + response.error); 57 | } 58 | 59 | return response.data; 60 | }; 61 | 62 | switch (type) { 63 | case ApiRequestType.get: 64 | return await callRequest("GET"); 65 | 66 | case ApiRequestType.post: 67 | return await callRequest("POST"); 68 | 69 | default: 70 | throw new Error(`unknown ApiRequestType, expect: {get, post}`); 71 | } 72 | } 73 | 74 | async getNewBlocks(limit = 10): Promise { 75 | return await this.httpRequest("get_new_blocks", { limit }); 76 | } 77 | 78 | async getBlockByTxHash(tx_hash: HexString): Promise { 79 | return await this.httpRequest("get_block_by_tx_hash", { tx_hash }); 80 | } 81 | 82 | async getLiveCells(query: QueryOption, limit: number = 10): Promise { 83 | return await this.httpRequest("get_live_cells", { 84 | query, 85 | limit, 86 | }); 87 | } 88 | 89 | async getWallets(): Promise { 90 | return await this.httpRequest("wallets"); 91 | } 92 | 93 | async getBalance(lock_args: HexString): Promise { 94 | return await this.httpRequest("get_balance", { 95 | lock_args, 96 | }); 97 | } 98 | 99 | async getTransactions( 100 | query: QueryOption, 101 | limit: number = 10 102 | ): Promise { 103 | return await this.httpRequest("get_txs", { 104 | query, 105 | limit, 106 | }); 107 | } 108 | 109 | async getChainConfig(): Promise { 110 | return await this.httpRequest("chain_config"); 111 | } 112 | 113 | async getSignature(message: string, private_key: string): Promise { 114 | return await this.httpRequest("get_signature", { 115 | message, 116 | private_key, 117 | }); 118 | } 119 | 120 | async getToSignMessage( 121 | raw_tx: RawTransaction, 122 | witnessArgs: WitnessArgs[] 123 | ): Promise { 124 | return await this.httpRequest("get_sign_message", { 125 | raw_tx, 126 | witnessArgs: JSON.stringify(witnessArgs), 127 | }); 128 | } 129 | 130 | async generateTxHash(raw_tx: RawTransaction): Promise { 131 | return await this.httpRequest("get_tx_hash", { 132 | raw_tx, 133 | }); 134 | } 135 | 136 | async generateSerializeTx(raw_tx: RawTransaction): Promise { 137 | return await this.httpRequest("get_serialize_tx", { 138 | raw_tx, 139 | }); 140 | } 141 | 142 | async sendTx(tx: Transaction): Promise { 143 | return await this.httpRequest("send_tx", { 144 | tx, 145 | }); 146 | } 147 | 148 | async getSerializedWitness(witnessArgs: WitnessArgs): Promise { 149 | return await this.httpRequest("get_serialized_witness", { 150 | witnessArgs, 151 | }); 152 | } 153 | 154 | async getMinimalCellCapacity(cell: Cell): Promise { 155 | return await this.httpRequest("get_minimal_cell_capacity", { 156 | cell, 157 | }); 158 | } 159 | } 160 | 161 | export default Api; 162 | -------------------------------------------------------------------------------- /src/components/home/Home.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #3cc68a; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | 40 | .arcii-art { 41 | white-space: pre; 42 | display: inline-block; 43 | font-family: "Lucida Console", Monaco, monospace; 44 | letter-spacing: -0.2em; 45 | line-height: 0.8em; 46 | text-shadow: 0 0 5px rgba(100, 100, 100, 0.5); 47 | font-size: 12px; 48 | } 49 | -------------------------------------------------------------------------------- /src/components/home/Home.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./Home.css"; 3 | 4 | function Home() { 5 | return ( 6 |
7 |
8 |
 9 |           {/* prettier-ignore */}
10 |            
11 |  __       _______     ___      .______     .__   __.      ______  __  ___ .______       _______ .______       ______   .___  ___.      ___          
12 | | | | ____| / \ | _ \ | \ | | / || |/ / | _ \ | ____|| _ \ / __ \ | \/ | / _ \
13 | | | | |__ / ^ \ | |_) | | \| | | ,----'| ' / | |_) | | |__ | |_) | | | | | | \ / | | | | |
14 | | | | __| / /_\ \ | / | . ` | | | | | _ | __| | / | | | | | |\/| | | | | |
15 | | `----.| |____ / _____ \ | |\ \----| |\ | | `----.| . \ | |_) | | | | |\ \----| `--' | | | | | | |_| |
16 | |_______||_______/__/ \__\ | _| `._____|__| \__| \______||__|\__\ |______/ |__| | _| `._____|\______/ |__| |__| \___/
17 |
18 |
19 |
20 |
21 | 27 | Let's Rock! 28 | 29 |
30 |
31 | ); 32 | } 33 | 34 | export default Home; 35 | -------------------------------------------------------------------------------- /src/components/home/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/toolbox/FloatingBox.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import "./style.css"; 3 | import commonStyle from "../widget/common_style"; 4 | import QueryCell from "./tools/queryCell"; 5 | import QueryTx from "./tools/queryTx"; 6 | import Hex2Decimal from "./tools/hex2decimal"; 7 | import Wallets from "../tutorial/sections/common/Wallets"; 8 | import ChainConfig from "../tutorial/sections/show_chain_info/ChainConfig"; 9 | import Logo from "../widget/logo_svg"; 10 | import NewLogo from "../../resource/nervos-token-logo-white.jpg"; 11 | import Api from "../../api/blockchain"; 12 | import type { ChainConfig as typeChainConfig } from "../../types/blockchain"; 13 | import { I18nComponentsProps } from "../../types/i18n"; 14 | 15 | const styles = { 16 | ...commonStyle, 17 | ...{ 18 | root: { 19 | position: "fixed" as const, 20 | right: "0px", 21 | top: "20%", 22 | }, 23 | hr: { 24 | backgroundColor: commonStyle.main_color.color, 25 | height: "1px", 26 | border: "0", 27 | }, 28 | tool_panel: { 29 | textAlign: "left" as const, 30 | overflowY: "scroll" as const, 31 | }, 32 | close_btn: { 33 | top: "0", 34 | width: "100%", 35 | textAlign: "right" as const, 36 | fontSize: "15px", 37 | background: commonStyle.main_color.color, 38 | outline: "none", 39 | border: "0", 40 | }, 41 | }, 42 | }; 43 | 44 | /** 45 | * todo: add clickable effect on close btn 46 | */ 47 | export default function FloatingBox(props: I18nComponentsProps) { 48 | const { t } = props; 49 | const [code_hash, setCodeHash] = useState(""); 50 | const [hash_type, setHashType] = useState(""); 51 | 52 | async function fetchChainConfig() { 53 | const api = new Api(); 54 | var config: typeChainConfig = await api.getChainConfig(); 55 | await setCodeHash(config.SCRIPTS.SECP256K1_BLAKE160.CODE_HASH); 56 | await setHashType(config.SCRIPTS.SECP256K1_BLAKE160.HASH_TYPE); 57 | } 58 | 59 | useEffect(() => { 60 | fetchChainConfig(); 61 | }, []); 62 | 63 | const [isQueryCellOpen, setIsQueryCellOpen] = useState(false); 64 | const [isQueryTxOpen, setIsQueryTxOpen] = useState(false); 65 | const [isWalletsOpen, setIsWalletsOpen] = useState(false); 66 | const [isConfigOpen, setIsConfigOpen] = useState(false); 67 | const [isCaculatorOpen, setIsCaculatorOpen] = useState(false); 68 | 69 | const [isOpen, setIsOpen] = useState(true); 70 | 71 | const openQueryCell = () => { 72 | setIsQueryCellOpen(true); 73 | setIsOpen(false); 74 | }; 75 | 76 | const openQueryTx = () => { 77 | setIsQueryTxOpen(true); 78 | setIsOpen(false); 79 | }; 80 | 81 | const openWallets = () => { 82 | setIsWalletsOpen(true); 83 | setIsOpen(false); 84 | }; 85 | 86 | const openConfig = () => { 87 | setIsConfigOpen(true); 88 | setIsOpen(false); 89 | }; 90 | 91 | const openCaculator = () => { 92 | setIsCaculatorOpen(true); 93 | setIsOpen(false); 94 | }; 95 | 96 | const hanlderClose = () => { 97 | setIsCaculatorOpen(false); 98 | setIsQueryTxOpen(false); 99 | setIsWalletsOpen(false); 100 | setIsConfigOpen(false); 101 | setIsQueryCellOpen(false); 102 | setIsOpen(true); 103 | }; 104 | 105 | return ( 106 |
107 | 113 |
114 | 154 |
155 | 164 |
165 |
166 | 171 |
172 |
173 | 174 |
175 |
176 | 177 |
178 |
179 | 180 |
181 |
182 | 183 |
184 |
185 |
186 | 191 |
192 |
193 | ); 194 | } 195 | -------------------------------------------------------------------------------- /src/components/toolbox/style.css: -------------------------------------------------------------------------------- 1 | .side-menu { 2 | z-index: 30; 3 | position: relative; 4 | width: 300px; 5 | min-height: 300px; 6 | max-height: 100%; 7 | box-sizing: border-box; 8 | background-color: black; 9 | display: flex; 10 | flex-direction: column; 11 | transform: translateX(102%); 12 | transition: 0.3s; 13 | } 14 | 15 | .side-menu li { 16 | margin: 0; 17 | padding: 0; 18 | list-style: none; 19 | width: 100%; 20 | } 21 | 22 | .side-menu ul { 23 | margin: 0; 24 | padding: 0; 25 | list-style: none; 26 | } 27 | 28 | .side-menu .form { 29 | display: flex; 30 | margin: 0 10px 50px; 31 | border-radius: 100px; 32 | border: 1px solid #fff; 33 | } 34 | 35 | .side-menu .form input, 36 | .side-menu .form button { 37 | border: none; 38 | background-color: transparent; 39 | color: #fff; 40 | padding: 5px 10px; 41 | } 42 | 43 | .side-menu .form input::placeholder { 44 | color: #fff; 45 | } 46 | 47 | .side-menu .form button { 48 | width: 50px; 49 | border-left: 2px solid #fff; 50 | cursor: pointer; 51 | } 52 | 53 | .side-menu .form button:hover { 54 | transform: scaleY(-1); 55 | } 56 | 57 | .side-menu .form input:focus, 58 | .side-menu .form button:focus { 59 | outline: none; 60 | } 61 | 62 | .side-menu label { 63 | position: absolute; 64 | width: 100px; 65 | height: 80px; 66 | background-color: #000; 67 | color: #fff; 68 | left: -100px; 69 | top: 0; 70 | bottom: 0; 71 | margin: auto; 72 | line-height: 80px; 73 | text-align: center; 74 | font-size: 3rem; 75 | border-radius: 20px 0px 0px 20px; 76 | } 77 | 78 | .side-menu label:hover { 79 | box-shadow: 0px 1px 1px #3cc68a; 80 | } 81 | 82 | #side-menu-switch { 83 | position: absolute; 84 | opacity: 0; 85 | z-index: 1; 86 | } 87 | 88 | #side-menu-switch:checked + .side-menu { 89 | transform: translateX(0); 90 | } 91 | 92 | #side-menu-switch:checked + .side-menu label .tool-icon { 93 | animation: spin 0.3s; 94 | } 95 | 96 | @keyframes spin { 97 | 0% { 98 | transform: rotate(0deg); 99 | } 100 | 100% { 101 | transform: rotate(720deg); 102 | } 103 | } 104 | 105 | /*選單*/ 106 | 107 | .nav a { 108 | display: block; 109 | padding: 10px; 110 | color: #fff; 111 | text-decoration: none; 112 | position: relative; 113 | font-family: "Noto Sans TC", sans-serif; 114 | cursor: pointer; 115 | } 116 | 117 | .nav li { 118 | position: relative; 119 | } 120 | 121 | .nav li + li > a::before { 122 | content: ""; 123 | position: absolute; 124 | border-bottom: 1px solid #fff; 125 | right: 10px; 126 | right: 10px; 127 | top: 0; 128 | } 129 | 130 | .nav a .fa { 131 | margin-right: -1.1em; 132 | transform: rotateY(0deg); 133 | opacity: 0; 134 | transition: 0.3s; 135 | } 136 | 137 | .nav li:hover .fa { 138 | margin-right: 0; 139 | opacity: 1; 140 | transform: rotateY(360deg); 141 | } 142 | 143 | .nav li:hover > a { 144 | background: #3cc68a; 145 | color: black; 146 | } 147 | 148 | .nav ul { 149 | z-index: 2; 150 | opacity: 0; 151 | pointer-events: none; 152 | position: absolute; 153 | text-align: left; 154 | right: 60%; 155 | width: 350px; 156 | top: 20%; 157 | background-color: rgba(255, 255, 255, 0.2); 158 | box-shadow: 5px 0 10px hsla(240, 40%, 15%, 0.6); 159 | transition: 0.3s; 160 | } 161 | 162 | .nav ul:hover { 163 | background-color: rgba(255, 255, 255, 0.5); 164 | } 165 | 166 | .nav li:hover > ul { 167 | pointer-events: auto; 168 | opacity: 1; 169 | right: 80%; 170 | } 171 | 172 | .nav ul li:hover > ul { 173 | right: 95%; 174 | } 175 | 176 | .nav ul ul li:hover > ul { 177 | right: 35%; 178 | } 179 | -------------------------------------------------------------------------------- /src/components/toolbox/tools/hex2decimal.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * this is a simple hex to decimal reverse converter component 3 | * to help user figure out the capacity number. 4 | */ 5 | 6 | import React, { useState, useRef } from "react"; 7 | import FreshButton from "../../widget/fresh_button"; 8 | import common_style from "../../widget/common_style"; 9 | import { CSSProperties } from "@material-ui/core/styles/withStyles"; 10 | import { notify } from "../../widget/notify"; 11 | import { I18nComponentsProps } from "../../../types/i18n"; 12 | import { validateParams, validators } from "../../../utils/validator"; 13 | import { errNotifyCallBack } from "../../tutorial/sections/common/callBacks"; 14 | 15 | const styles = { 16 | caculator_box: { 17 | border: "1px solid white", 18 | padding: "20px", 19 | }, 20 | input: { 21 | outline: common_style.main_color.color, 22 | padding: "10px", 23 | background: "white", 24 | width: "90%", 25 | borderRadius: "3px", 26 | border: "1px solid white", 27 | fontSize: "16px", 28 | }, 29 | btn: { 30 | marginRight: "5px", 31 | }, 32 | result: { 33 | border: "1px solid white", 34 | padding: "10px", 35 | fontSize: "16px", 36 | width: "90%", 37 | height: "100%", 38 | overflowWrap: "anywhere" as const, 39 | }, 40 | }; 41 | 42 | export interface Hex2DecProps extends I18nComponentsProps { 43 | custom_style?: CSSProperties; 44 | } 45 | 46 | export default function Hex2Dec(props: Hex2DecProps) { 47 | const { t, custom_style } = props; 48 | 49 | const ref = useRef(null); 50 | const [isReversed, setIsReversed] = useState(false); 51 | const [result, setResult] = useState(""); 52 | 53 | const reverse = () => { 54 | setIsReversed(!isReversed); 55 | }; 56 | 57 | const hex2dec = () => { 58 | if (ref.current) { 59 | const hex_data = ref.current.value; 60 | try { 61 | validateParams([hex_data], [validators.hexNumber]); 62 | setResult("" + BigInt(hex_data).toString(10)); 63 | } catch (error: any) { 64 | notify(error.message); 65 | } 66 | } else { 67 | notify("something went wrong.."); 68 | } 69 | }; 70 | 71 | const dec2hex = () => { 72 | if (ref.current) { 73 | const dec_data = ref.current.value; 74 | try { 75 | validateParams([dec_data], [validators.decimalPositiveIntegerString]); 76 | setResult("0x" + BigInt(dec_data).toString(16)); 77 | } catch (error: any) { 78 | notify(error.message); 79 | } 80 | } else { 81 | notify("something went wrong.."); 82 | } 83 | }; 84 | 85 | return ( 86 |
93 |

94 | Convert {isReversed ? "Decimal" : "Hex"} to{" "} 95 | {isReversed ? "Hex" : "Decimal"}{" "} 96 |

97 | 107 |

108 | 112 |   113 | 119 |

120 |
121 | {t("tutorial.widget.toolBox.hexToDecimal.resultText")} {result} 122 |
123 |
124 | ); 125 | } 126 | -------------------------------------------------------------------------------- /src/components/toolbox/tools/queryCell.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState, useEffect } from "react"; 2 | import { QueryOption } from "../../../types/blockchain"; 3 | import { I18nComponentsProps } from "../../../types/i18n"; 4 | import { validateParams, validators } from "../../../utils/validator"; 5 | import { errNotifyCallBack } from "../../tutorial/sections/common/callBacks"; 6 | import Cells from "../../tutorial/sections/common/Cells"; 7 | import commonStyle from "../../widget/common_style"; 8 | 9 | const styles = { 10 | ...commonStyle, 11 | ...{ 12 | search_bar: { 13 | width: "80%", 14 | padding: "5px", 15 | margin: "0 auto", 16 | }, 17 | input: { 18 | fontSize: "18px", 19 | outline: "none", 20 | width: "100%", 21 | }, 22 | }, 23 | }; 24 | 25 | export interface QueryCellProps extends I18nComponentsProps { 26 | code_hash?: string; 27 | hash_type?: "type" | "data"; 28 | } 29 | 30 | export default function QueryCell(props: QueryCellProps) { 31 | const { t, code_hash, hash_type } = props; 32 | 33 | const ref = useRef(null); 34 | const [query, setQuery] = useState(); 35 | 36 | const startQuery = () => { 37 | const args = ref.current?.value; 38 | validateParams([args], [validators.hexString], errNotifyCallBack); 39 | setQuery({ 40 | lock: { 41 | code_hash: 42 | code_hash || 43 | "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 44 | args: args!, 45 | hash_type: hash_type || "type", 46 | }, 47 | }); 48 | }; 49 | 50 | return ( 51 |
52 |
53 |
54 | 62 | 63 |
64 |
65 |
66 |
67 | {query && ( 68 | 78 | )} 79 |
80 |
81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /src/components/toolbox/tools/queryTx.tsx: -------------------------------------------------------------------------------- 1 | import { Divider } from "@material-ui/core"; 2 | import React, { useRef, useState } from "react"; 3 | import { QueryOption } from "../../../types/blockchain"; 4 | import { I18nComponentsProps } from "../../../types/i18n"; 5 | import { validateParams, validators } from "../../../utils/validator"; 6 | import { errNotifyCallBack } from "../../tutorial/sections/common/callBacks"; 7 | import Txs from "../../tutorial/sections/show_chain_info/WalletTransaction"; 8 | import commonStyle from "../../widget/common_style"; 9 | 10 | const styles = { 11 | ...commonStyle, 12 | ...{ 13 | search_bar: { 14 | width: "80%", 15 | padding: "5px", 16 | margin: "0 auto", 17 | }, 18 | input: { 19 | fontSize: "18px", 20 | outline: "none", 21 | width: "100%", 22 | }, 23 | }, 24 | }; 25 | 26 | export interface QueryTxProps extends I18nComponentsProps { 27 | code_hash?: string; 28 | hash_type?: "type" | "data"; 29 | } 30 | 31 | export default function QueryTx(props: QueryTxProps) { 32 | const { t, code_hash, hash_type } = props; 33 | 34 | const ref = useRef(null); 35 | const [query, setQuery] = useState(); 36 | 37 | const startQuery = () => { 38 | const args = ref.current?.value; 39 | validateParams([args], [validators.hexString], errNotifyCallBack); 40 | setQuery({ 41 | lock: { 42 | code_hash: 43 | code_hash || 44 | "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 45 | args: args!, 46 | hash_type: hash_type || "type", 47 | }, 48 | }); 49 | }; 50 | 51 | return ( 52 |
53 |
54 |
55 | 61 | 62 |
63 |
64 |
65 |
66 | {query && ( 67 | 76 | )} 77 |
78 |
79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /src/components/tutorial/Learn.tsx: -------------------------------------------------------------------------------- 1 | import { FeedBack } from "../widget/feedback/feedback"; 2 | import { DndProvider } from "react-dnd"; 3 | import { HTML5Backend } from "react-dnd-html5-backend"; 4 | import { useTranslation } from "react-i18next"; 5 | import { Container, Grid } from "@material-ui/core"; 6 | 7 | import Notify from "../widget/notify"; 8 | import Class1 from "./sections/Class1"; 9 | import Class2 from "./sections/Class2"; 10 | import Class3 from "./sections/Class3"; 11 | import Class4 from "./sections/Class4"; 12 | import ToolBox from "../toolbox/FloatingBox"; 13 | import PreKnowledge from "./sections/PreKnowledge"; 14 | import ShowChainInfo from "./sections/ShowChainInfo"; 15 | import AlertMessager from "../widget/alert_messager"; 16 | import TableOfContents from "../widget/table_of_contents"; 17 | import BeforeWeGetStarted from "./sections/BeforeWeGetStarted"; 18 | 19 | import styles from "../widget/common_style"; 20 | 21 | function Learn() { 22 | const { t, i18n } = useTranslation(); 23 | 24 | return ( 25 |
30 | 31 | 32 | 33 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
51 |
52 |
53 |
54 |
55 |
56 |

{t("tutorial.context.moreClass")}

57 |
58 | 59 | 60 | 61 |
62 |
63 | 64 |
65 | 66 | 67 |
68 |
69 |
70 |
71 |
72 | ); 73 | } 74 | 75 | export default Learn; 76 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/BeforeWeGetStarted.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { I18nComponentsProps } from "../../../types/i18n"; 3 | import styles from "../../widget/common_style"; 4 | 5 | export default function BeforeWeGetStarted(props: I18nComponentsProps) { 6 | const { t } = props; 7 | return ( 8 |
9 |

Before We Get Started

10 |
11 |
12 |

{t("tutorial.common.beforeWeGetStarted.p1")}

13 |

{t("tutorial.common.beforeWeGetStarted.p2")}

14 |

{t("tutorial.common.beforeWeGetStarted.p3")}

15 |
    16 |
  • {t("tutorial.common.beforeWeGetStarted.ul1.l1")}
  • 17 |
  • {t("tutorial.common.beforeWeGetStarted.ul1.l2")}
  • 18 |
  • {t("tutorial.common.beforeWeGetStarted.ul1.l3")}
  • 19 |
  • {t("tutorial.common.beforeWeGetStarted.ul1.l4")}
  • 20 |
21 |

{t("tutorial.common.beforeWeGetStarted.p4")}

22 | 23 |
    24 |
  • {t("tutorial.common.beforeWeGetStarted.ul2.l1")}
  • 25 |
  • {t("tutorial.common.beforeWeGetStarted.ul2.l2")}
  • 26 |
  • {t("tutorial.common.beforeWeGetStarted.ul2.l3")}
  • 27 |
28 |
29 |

{t("tutorial.common.beforeWeGetStarted.p5")}

30 |

{t("tutorial.common.beforeWeGetStarted.p6")}

31 |

{t("tutorial.common.beforeWeGetStarted.p7")}

32 |
33 |
34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/Class2.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { I18nComponentsProps } from "../../../types/i18n"; 3 | import styles from "../../widget/common_style"; 4 | 5 | export default function (props: I18nComponentsProps) { 6 | const { t } = props; 7 | return ( 8 |
9 |
10 |

{t("tutorial.class2.title")}

11 |

{t("tutorial.context.toBeContinue")}

12 |
13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/Class3.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { I18nComponentsProps } from "../../../types/i18n"; 3 | import styles from "../../widget/common_style"; 4 | 5 | export default function (props: I18nComponentsProps) { 6 | const { t } = props; 7 | return ( 8 |
9 |
10 |

{t("tutorial.class3.title")}

11 |

{t("tutorial.context.toBeContinue")}

12 |
13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/Class4.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { I18nComponentsProps } from "../../../types/i18n"; 3 | import styles from "../../widget/common_style"; 4 | 5 | export default function (props: I18nComponentsProps) { 6 | const { t } = props; 7 | return ( 8 |
9 |
10 |

{t("tutorial.class4.title")}

11 |

{t("tutorial.context.toBeContinue")}

12 |
13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/ShowChainInfo.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Wallets from "./common/Wallets"; 3 | import ChainConfig from "./show_chain_info/ChainConfig"; 4 | import WalletCells from "./show_chain_info/WalletCells"; 5 | import NewBlocks from "./show_chain_info/new_blocks"; 6 | import styles from "../../widget/common_style"; 7 | import type { Wallet } from "../../../types/blockchain"; 8 | import { I18nComponentsProps } from "../../../types/i18n"; 9 | 10 | export default function (props: I18nComponentsProps) { 11 | const { t } = props; 12 | 13 | const [wallets, setWallets] = useState([]); 14 | 15 | return ( 16 |
17 |
18 |
19 |

20 | {t("tutorial.common.getYourHandDirty.title")} 21 |

22 |
23 | {t("tutorial.common.getYourHandDirty.p1")} 24 |
25 |
26 |
27 | 28 |
29 |

{t("tutorial.common.getYourHandDirty.studyAChain.p1")}

30 |

{t("tutorial.common.getYourHandDirty.studyAChain.p2")}

31 |
32 | 33 |
34 | 35 | 36 |
37 |

{t("tutorial.common.getYourHandDirty.studyAChain.p3")}

38 |

{t("tutorial.common.getYourHandDirty.studyAChain.p4")}

39 |
40 | 41 | 42 | 43 |
44 |
45 |

{t("tutorial.common.getYourHandDirty.studyAChain.p5")}

46 |
    47 |
  • 48 | {t("tutorial.common.getYourHandDirty.studyAChain.ul1.l1")} 49 |
  • 50 |
  • 51 | {t("tutorial.common.getYourHandDirty.studyAChain.ul1.l2")} 52 |
  • 53 |
  • 54 | {t("tutorial.common.getYourHandDirty.studyAChain.ul1.l3")} 55 |
  • 56 |
  • 57 | {t("tutorial.common.getYourHandDirty.studyAChain.ul1.l4")} 58 |
  • 59 |
60 |

{t("tutorial.common.getYourHandDirty.studyAChain.p6")}

61 |
62 |
63 |

{t("tutorial.common.getYourHandDirty.studyAChain.p7")}

64 |
65 | 66 |
67 | 68 |
69 | 70 |
71 |
72 |

{t("tutorial.common.getYourHandDirty.studyAChain.p8")}

73 |

{t("tutorial.common.getYourHandDirty.studyAChain.p9")}

74 |

{t("tutorial.common.getYourHandDirty.studyAChain.p10")}

75 |

{t("tutorial.common.getYourHandDirty.studyAChain.p11")}

76 |

{t("tutorial.common.getYourHandDirty.studyAChain.p12")}

77 |
78 |
79 |
80 |
81 |

{t("tutorial.common.getYourHandDirty.studyAChain.p13")}

82 | 83 |
84 |

85 | prefix:ckt{" "} 86 | {t("tutorial.common.getYourHandDirty.studyAChain.p15")} 87 |

88 |

89 | scripts{" "} 90 | {t("tutorial.common.getYourHandDirty.studyAChain.p16")} 91 |

92 |

{t("tutorial.common.getYourHandDirty.studyAChain.p17")}

93 |
    94 |
  • 95 | {t("tutorial.common.getYourHandDirty.studyAChain.ul2.l1")} 96 |
  • 97 |
  • 98 | {t("tutorial.common.getYourHandDirty.studyAChain.ul2.l2")} 99 |
  • 100 |
  • 101 | {t("tutorial.common.getYourHandDirty.studyAChain.ul2.l3")} 102 |
  • 103 |
104 |
105 |
106 |

{t("tutorial.common.getYourHandDirty.studyAChain.p18")}

107 |

{t("tutorial.common.getYourHandDirty.studyAChain.p19")}

108 |
109 |
110 |
111 | ); 112 | } 113 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/class_1/CompleteTxWithWitness.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from "react"; 2 | import { RawTransaction, Transaction } from "../../../../types/blockchain"; 3 | import commonStyle from "../../../widget/common_style"; 4 | import CodePiece from "../../../widget/code"; 5 | import FreshButton from "../../../widget/fresh_button"; 6 | import { I18nComponentsProps } from "../../../../types/i18n"; 7 | 8 | const styles = { 9 | ...commonStyle, 10 | ...{ 11 | input_wrap: { 12 | padding: "5px", 13 | marginBottom: "10px", 14 | display: "block", 15 | background: "white", 16 | }, 17 | input: { 18 | width: "100%", 19 | outline: "none", 20 | lineHeight: "2em", 21 | fontSize: "16px", 22 | border: "0", 23 | }, 24 | root: { 25 | width: "100%", 26 | padding: "10px 0px", 27 | }, 28 | result: { 29 | padding: "10px", 30 | border: "1px solid gray", 31 | marginTop: "5px", 32 | overflowWrap: "break-word" as const, 33 | }, 34 | }, 35 | }; 36 | 37 | export interface CompleteTxWithWitnessProps extends I18nComponentsProps { 38 | raw_tx: RawTransaction; 39 | onCallBack?: (tx: Transaction) => void; 40 | } 41 | 42 | export default function CompleteTxWithWitness( 43 | props: CompleteTxWithWitnessProps 44 | ) { 45 | const { t, raw_tx, onCallBack } = props; 46 | const [tx, setTx] = useState(); 47 | const ref = useRef(null); 48 | 49 | const onCompleteTx = () => { 50 | var rtx = eval( 51 | "`" + 52 | JSON.stringify(raw_tx).substring(1, JSON.stringify(raw_tx).length - 1) + 53 | "`" 54 | ); 55 | rtx = JSON.parse(JSON.stringify(rtx)); 56 | const myTx = { 57 | ...rtx, 58 | ...{ 59 | witnesses: [ref.current?.value], 60 | }, 61 | }; 62 | setTx(myTx); 63 | 64 | if (onCallBack && myTx) onCallBack(myTx); 65 | }; 66 | 67 | return ( 68 |
69 | 70 | 76 | 77 | 82 |
83 | 87 |
88 |
89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/class_1/FetchToSignMessage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { 3 | HexString, 4 | RawTransaction, 5 | Message, 6 | } from "../../../../types/blockchain"; 7 | import FreshButton from "../../../widget/fresh_button"; 8 | import Api from "../../../../api/blockchain"; 9 | import { notify } from "../../../widget/notify"; 10 | import commonStyle from "../../../widget/common_style"; 11 | import { I18nComponentsProps } from "../../../../types/i18n"; 12 | 13 | const styles = { 14 | ...commonStyle, 15 | ...{ 16 | root: { 17 | width: "100%", 18 | padding: "10px 0px", 19 | }, 20 | result: { 21 | padding: "10px", 22 | border: "1px solid gray", 23 | marginTop: "5px", 24 | overflowWrap: "break-word" as const, 25 | }, 26 | }, 27 | }; 28 | 29 | export interface FetchToSignMessageProps extends I18nComponentsProps { 30 | raw_tx: RawTransaction | undefined; 31 | witnesses?: HexString[]; 32 | } 33 | 34 | export default function FetchToSignMessage(props: FetchToSignMessageProps) { 35 | const { t } = props; 36 | const [isLoading, setIsLoading] = useState(false); 37 | const [message, setMessage] = useState(""); 38 | const generateMessage = async () => { 39 | setIsLoading(true); 40 | //const raw_tx = JSON.parse(JSON.stringify(eval("(" + props.raw_tx + ")"))); 41 | //console.log(raw_tx); 42 | if (props.raw_tx) { 43 | const api = new Api(); 44 | try { 45 | const result = await api.getToSignMessage( 46 | props.raw_tx, 47 | props.witnesses 48 | ? props.witnesses 49 | : Array(props.raw_tx.inputs?.length).fill("0x") 50 | ); 51 | console.log(result, typeof result); 52 | const msgs = result.map((m: Message) =>
  • {m.message}
  • ); 53 | setMessage(msgs); 54 | } catch (error: any) { 55 | notify(error.message); 56 | } 57 | } else { 58 | notify(t("tutorial.widget.toSignMessage.txUndefinedAlertMsg")); 59 | } 60 | setIsLoading(false); 61 | }; 62 | return ( 63 |
    64 | 70 |
    71 |

    {message}

    72 |
    73 |
    74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/class_1/SendTx.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import FreshButton from "../../../widget/fresh_button"; 3 | import { notify } from "../../../widget/notify"; 4 | import Api from "../../../../api/blockchain"; 5 | import commonStyle from "../../../widget/common_style"; 6 | import type { Block, Transaction } from "../../../../types/blockchain"; 7 | import BlockBox from "../common/Block"; 8 | import Firework from "../../../widget/fireworks/firework"; 9 | import PopFirework from "../../../widget/fireworks/animate"; 10 | import { I18nComponentsProps } from "../../../../types/i18n"; 11 | 12 | const styles = { 13 | ...commonStyle, 14 | ...{ 15 | root: { 16 | width: "100%", 17 | padding: "10px 0px", 18 | clear: "both" as const, 19 | }, 20 | result: { 21 | padding: "10px", 22 | border: "1px solid gray", 23 | marginTop: "5px", 24 | overflowWrap: "break-word" as const, 25 | }, 26 | block_panel: { 27 | padding: "10px", 28 | border: "1px solid gray", 29 | marginTop: "5px", 30 | textAlign: "center" as const, 31 | clear: "both" as const, 32 | }, 33 | }, 34 | }; 35 | 36 | export interface SendTxProps extends I18nComponentsProps { 37 | tx: Transaction | undefined; 38 | } 39 | 40 | export default function SendTx(props: SendTxProps) { 41 | const { t, tx } = props; 42 | const [tx_hash, setTxHash] = useState(""); 43 | const [block, setBlock] = useState(); 44 | 45 | const sendTx = async () => { 46 | const api = new Api(); 47 | if (tx) { 48 | try { 49 | const txHash = await api.sendTx(tx); 50 | setTxHash(txHash); 51 | PopFirework(); 52 | } catch (error: any) { 53 | notify(error.message); 54 | } 55 | } else { 56 | notify(t("tutorial.widget.sendTx.txUndefinedAlertMsg")); 57 | } 58 | }; 59 | 60 | const fetchBlock = async () => { 61 | if (!tx_hash) { 62 | notify("tx hash is empty."); 63 | return; 64 | } 65 | 66 | const api = new Api(); 67 | try { 68 | const block = await api.getBlockByTxHash(tx_hash); 69 | setBlock(block); 70 | } catch (error: any) { 71 | notify(error.message); 72 | } 73 | }; 74 | 75 | return ( 76 |
    77 | 82 |
    83 |

    tx_hash: {tx_hash}

    84 |
    85 |
    86 |

    {t("tutorial.widget.sendTx.p1")}

    87 |

    {t("tutorial.widget.sendTx.p2")}

    88 |

    {t("tutorial.widget.sendTx.p3")}

    89 |
    90 | 91 | 96 |
    97 |

    98 | {block && ( 99 | 104 | )} 105 |

    106 |
    107 | 108 |
    109 | ); 110 | } 111 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/class_1/SerializedWitnessArgs.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from "react"; 2 | import { notify } from "../../../widget/notify"; 3 | import Api from "../../../../api/blockchain"; 4 | import commonStyle from "../../../widget/common_style"; 5 | import FreshButton from "../../../widget/fresh_button"; 6 | import { I18nComponentsProps } from "../../../../types/i18n"; 7 | import { validateParams, validators } from "../../../../utils/validator"; 8 | 9 | const styles = { 10 | ...commonStyle, 11 | ...{ 12 | input_wrap: { 13 | padding: "5px", 14 | marginBottom: "10px", 15 | display: "block", 16 | background: "white", 17 | }, 18 | input: { 19 | width: "100%", 20 | outline: "none", 21 | lineHeight: "2em", 22 | fontSize: "16px", 23 | border: "0", 24 | }, 25 | root: { 26 | width: "100%", 27 | padding: "10px 0px", 28 | }, 29 | result: { 30 | padding: "10px", 31 | border: "1px solid gray", 32 | marginTop: "5px", 33 | overflowWrap: "break-word" as const, 34 | }, 35 | }, 36 | }; 37 | 38 | export default function SerializedWitnessArgs(props: I18nComponentsProps) { 39 | const { t } = props; 40 | const [witness, setWitness] = useState(""); 41 | const lock_ref = useRef(null); 42 | 43 | const serialized_witness = async () => { 44 | const api = new Api(); 45 | try { 46 | const lockWitValue = lock_ref.current?.value; 47 | validateParams([lockWitValue], [validators.hexString]); 48 | const witnessArgs = { 49 | lock: lock_ref.current?.value, 50 | }; 51 | const res = await api.getSerializedWitness(witnessArgs); 52 | setWitness(res); 53 | } catch (error: any) { 54 | notify(error.message); 55 | } 56 | }; 57 | 58 | return ( 59 |
    60 | 61 | 69 | 70 | 75 |
    76 |

    {witness}

    77 |
    78 |
    79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/class_1/Signer.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from "react"; 2 | import FreshButton from "../../../widget/fresh_button"; 3 | import Api from "../../../../api/blockchain"; 4 | import { notify } from "../../../widget/notify"; 5 | import commonStyle from "../../../widget/common_style"; 6 | import { I18nComponentsProps } from "../../../../types/i18n"; 7 | import { validateParams, validators } from "../../../../utils/validator"; 8 | 9 | const styles = { 10 | ...commonStyle, 11 | ...{ 12 | input_wrap: { 13 | padding: "5px", 14 | marginBottom: "10px", 15 | display: "block", 16 | background: "white", 17 | }, 18 | input: { 19 | width: "100%", 20 | outline: "none", 21 | lineHeight: "2em", 22 | fontSize: "16px", 23 | border: "0", 24 | }, 25 | root: { 26 | width: "100%", 27 | padding: "10px 0px", 28 | }, 29 | result: { 30 | padding: "10px", 31 | border: "1px solid gray", 32 | marginTop: "5px", 33 | overflowWrap: "break-word" as const, 34 | }, 35 | }, 36 | }; 37 | 38 | export default function Signer(props: I18nComponentsProps) { 39 | const { t } = props; 40 | const [isLoading, setIsLoading] = useState(false); 41 | const [signature, setSignature] = useState(""); 42 | const msg_ref = useRef(null); 43 | const key_ref = useRef(null); 44 | 45 | const sign_message = async () => { 46 | setIsLoading(true); 47 | const msg = msg_ref.current?.value || ""; 48 | const key = key_ref.current?.value || ""; 49 | const api = new Api(); 50 | 51 | try { 52 | validateParams( 53 | [msg, key], 54 | [validators.ckbToSignMessage, validators.ckbPrivateKey] 55 | ); 56 | const sig = await api.getSignature(msg, key); 57 | setSignature(sig); 58 | } catch (error: any) { 59 | notify(error.message); 60 | } 61 | 62 | setIsLoading(false); 63 | }; 64 | 65 | return ( 66 |
    67 | 68 | 74 | 75 | 76 | 82 | 83 | 89 |
    90 |

    {signature}

    91 |
    92 |
    93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/class_1/ToTxHash.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Api from "../../../../api/blockchain"; 3 | import { RawTransaction } from "../../../../types/blockchain"; 4 | import FreshButton from "../../../widget/fresh_button"; 5 | import { notify } from "../../../widget/notify"; 6 | import CodePiece from "../../../widget/code"; 7 | import { I18nComponentsProps } from "../../../../types/i18n"; 8 | 9 | const styles = { 10 | root: { 11 | width: "100%", 12 | padding: "10px 0px", 13 | }, 14 | result: { 15 | padding: "10px", 16 | border: "1px solid gray", 17 | marginTop: "5px", 18 | overflowWrap: "break-word" as const, 19 | }, 20 | }; 21 | 22 | export interface Props extends I18nComponentsProps { 23 | raw_tx: RawTransaction | undefined; 24 | } 25 | 26 | export default function ToTxHash(props: Props) { 27 | const { t } = props; 28 | const [hash, setHash] = useState(""); 29 | const [serializeTx, setSerializeTx] = useState(""); 30 | 31 | const generateSerializeTx = async () => { 32 | const api = new Api(); 33 | if (props.raw_tx) { 34 | try { 35 | const res = await api.generateSerializeTx(props.raw_tx); 36 | console.log(res); 37 | setSerializeTx(res); 38 | } catch (error: any) { 39 | notify(error.message); 40 | } 41 | } else { 42 | notify(t("tutorial.widget.toTxHash.txUndefinedAlertMsg")); 43 | } 44 | }; 45 | 46 | const generateTxHash = async () => { 47 | await generateSerializeTx(); 48 | 49 | const api = new Api(); 50 | if (props.raw_tx) { 51 | try { 52 | const res = await api.generateTxHash(props.raw_tx); 53 | console.log(res); 54 | setHash(res); 55 | } catch (error: any) { 56 | notify(error.message); 57 | } 58 | } else { 59 | notify(t("tutorial.widget.toTxHash.txUndefinedAlertMsg")); 60 | } 61 | }; 62 | 63 | return ( 64 |
    65 |
    66 | {t("tutorial.widget.toTxHash.serializedTxBeforeHash")} 67 | 68 |
    69 | 74 |
    75 |

    tx_hash: {hash}

    76 |
    77 |
    78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/common/Block.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties, useState } from "react"; 2 | import { Container } from "@material-ui/core"; 3 | import Utils from "../../../../utils/index"; 4 | import common_style from "../../../widget/common_style"; 5 | import type { Block, Transaction } from "../../../../types/blockchain"; 6 | import ShowTxInfo from "./Tx"; 7 | import { I18nComponentsProps } from "../../../../types/i18n"; 8 | 9 | export interface BlockProps extends I18nComponentsProps { 10 | block: Block; 11 | custom_style?: CSSProperties; 12 | } 13 | 14 | const styles = { 15 | ...common_style, 16 | ...{ 17 | box: { 18 | maxWidth: "200px", 19 | maxHeight: "200px", 20 | border: "1px solid white", 21 | margin: "0.5em", 22 | float: "left" as const, 23 | display: "box", 24 | textAlign: "left" as const, 25 | overflowY: "scroll" as const, 26 | }, 27 | hover_box: { 28 | maxWidth: "200px", 29 | maxHeight: "200px", 30 | border: "1px solid white", 31 | margin: "0.5em", 32 | float: "left" as const, 33 | display: "box", 34 | textAlign: "left" as const, 35 | overflowY: "scroll" as const, 36 | background: "gray", 37 | }, 38 | box_header: { 39 | width: "100%", 40 | overflow: "hidden", 41 | textOverflow: "ellipsis" as const, 42 | whiteSpace: "nowrap" as const, 43 | }, 44 | box_header_title: { 45 | fontSize: "15px", 46 | fontWeight: "bolder" as const, 47 | color: common_style.main_color.color.toString(), 48 | marginTop: "10px", 49 | marginBottom: "10px", 50 | }, 51 | box_header_sub_title: { 52 | fontSize: "12px", 53 | margin: "0", 54 | }, 55 | box_content: { 56 | width: "100%", 57 | overflow: "hidden", 58 | }, 59 | box_content_link: { 60 | textDecoration: "underline", 61 | cursor: "pointer", 62 | }, 63 | }, 64 | }; 65 | 66 | export default function BlockBox(props: BlockProps) { 67 | const { t } = props; 68 | const [isHover, setIsHover] = useState(false); 69 | 70 | const hovering = () => { 71 | setIsHover(true); 72 | }; 73 | 74 | const unHover = () => { 75 | setIsHover(false); 76 | }; 77 | 78 | const { block, custom_style } = props; 79 | 80 | function show_tx_list(transactions: Transaction[]) { 81 | const jsx = transactions.map((tx: Transaction, index: number) => ( 82 | 83 | )); 84 | return jsx; 85 | } 86 | 87 | const box_style = isHover ? styles.hover_box : styles.box; 88 | 89 | return ( 90 |
    91 | 97 |
    98 |
    99 | {" "} 100 | {t("tutorial.widget.block.blockText")} # 101 | {Utils.hex2dec(block.header.number)}{" "} 102 |
    103 |

    104 | {t("tutorial.widget.block.hashText")}{" "} 105 | {block.header.hash.slice(0, 12)}..{" "} 106 |

    107 |

    108 | {t("tutorial.widget.block.timeText")}{" "} 109 | {Utils.convertTimestamp( 110 | BigInt(block.header.timestamp).toString(10) 111 | )}{" "} 112 |

    113 |
    114 |
    115 |

    116 | {t("tutorial.widget.block.transactionCount", { 117 | count: block.transactions.length, 118 | })} 119 |

    120 |
    121 |
      {show_tx_list(block.transactions)}
    122 |
    123 |
    124 |
    125 | ); 126 | } 127 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/common/Blocks.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import type { Block } from "../../../../types/blockchain"; 3 | import { I18nComponentsProps } from "../../../../types/i18n"; 4 | import common_style from "../../../widget/common_style"; 5 | import BlockBox from "./Block"; 6 | 7 | export interface BlocksProps extends I18nComponentsProps { 8 | blocks: Block[]; 9 | } 10 | 11 | function Blocks(props: BlocksProps) { 12 | const { t } = props; 13 | const [blocks, setBlocks] = useState(props.blocks); 14 | 15 | useEffect(() => { 16 | setBlocks(props.blocks); 17 | }, [props.blocks]); 18 | 19 | const blocks_box = blocks.map((block: Block) => ( 20 | 21 | )); 22 | 23 | return
    {blocks_box}
    ; 24 | } 25 | 26 | export default Blocks; 27 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/common/CapacityOfCell.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties, useEffect, useState } from "react"; 2 | import { Cell } from "../../../../types/blockchain"; 3 | import commonStyle from "../../../widget/common_style"; 4 | import CodePiece from "../../../widget/code"; 5 | import Api from "../../../../api/blockchain"; 6 | import { notify } from "../../../widget/notify"; 7 | import utils from "../../../../utils/index"; 8 | import { Modal, Fade } from "@material-ui/core"; 9 | import { I18nComponentsProps } from "../../../../types/i18n"; 10 | 11 | const styles = { 12 | ...commonStyle, 13 | ...{ 14 | root: { 15 | width: "100%", 16 | clear: "both" as const, 17 | textAlign: "center" as const, 18 | margin: "2em 0", 19 | border: "1px solid gray", 20 | padding: "10px", 21 | }, 22 | cell_panel: { 23 | width: "208px", 24 | height: "208px", 25 | listStyle: "none", 26 | margin: "0 auto", 27 | marginBottom: "10px", 28 | padding: "10px", 29 | borderRadius: "100%", 30 | }, 31 | ball: { 32 | width: "100%", 33 | height: "100%", 34 | borderRadius: "100%", 35 | border: "1px solid " + commonStyle.main_color.color, 36 | textAlign: "center" as const, 37 | justifyContent: "center" as const, 38 | fontSize: "14px", 39 | alignItems: "center" as const, 40 | overflow: "hidden" as const, 41 | }, 42 | ball_hover: { 43 | background: "gray", 44 | cursor: "pointer", 45 | }, 46 | cell_content: { 47 | margin: "20% auto", 48 | overflowWrap: "break-word" as const, 49 | }, 50 | space_result: { 51 | textAlign: "left" as const, 52 | }, 53 | hr: { 54 | display: "block", 55 | height: "1px", 56 | border: "0", 57 | borderTop: "1px solid " + commonStyle.main_color.color, 58 | }, 59 | }, 60 | }; 61 | 62 | export interface CapacityOfCellProps extends I18nComponentsProps { 63 | cell: Cell; 64 | custom_style?: CSSProperties; 65 | } 66 | 67 | // todo: make input only accept chinese word or calculate the english text as well. 68 | 69 | export default function CapacityOfCell(props: CapacityOfCellProps) { 70 | const { t, cell } = props; 71 | const [myCell, setMyCell] = useState(cell); 72 | const [totalByteLength, setTotalByteLength] = useState("61"); 73 | 74 | const [open, setOpen] = useState(false); 75 | 76 | const handleOpen = () => { 77 | setOpen(!open); 78 | }; 79 | 80 | const handleClose = () => { 81 | setOpen(!open); 82 | }; 83 | 84 | const [isHover, setIsHover] = useState(false); 85 | const hovering = () => { 86 | setIsHover(true); 87 | }; 88 | const unHover = () => { 89 | setIsHover(false); 90 | }; 91 | 92 | const handleInputChange = (text: string) => { 93 | const data = "0x" + toHex(text); 94 | setMyCell({ ...myCell, ...{ data: data } }); 95 | 96 | const dataLength = getByteLengthOfHexString(data); 97 | setTotalByteLength((61 + dataLength).toString()); 98 | }; 99 | 100 | const getByteLengthOfHexString = (str: string) => { 101 | var s = str.length - 2; //remove 0x 102 | return s / 2; 103 | }; 104 | 105 | const getCellPropertyByteLength = () => { 106 | const b2 = getByteLengthOfHexString(myCell.cell_output.lock.args); 107 | const b5 = getByteLengthOfHexString(myCell.data); 108 | return { 109 | capacity: "8 Bytes", 110 | lock: { 111 | args: b2 + " Bytes", 112 | code_hash: "32 Bytes", 113 | hash_type: "1 Bytes", 114 | }, 115 | data: b5 + " Bytes", 116 | }; 117 | }; 118 | 119 | function toHex(str: string) { 120 | var result = ""; 121 | for (var i = 0; i < str.length; i++) { 122 | result += str.charCodeAt(i).toString(16); 123 | } 124 | return result; 125 | } 126 | 127 | const isCapacityEnough = 128 | BigInt(utils.shannon2CKB(utils.hex2dec(myCell.cell_output.capacity))) > 129 | BigInt(totalByteLength) 130 | ? `capacity: ${ 131 | myCell.cell_output.capacity 132 | }(shannon) = ${utils.shannon2CKB( 133 | utils.hex2dec(myCell.cell_output.capacity) 134 | )}(CKB) > ${t( 135 | "tutorial.widget.capacityOfCell.actualCapacity" 136 | )}:${totalByteLength}(CKB), ✅` 137 | : `capacity: ${ 138 | myCell.cell_output.capacity 139 | }(shannon) = ${utils.shannon2CKB( 140 | utils.hex2dec(myCell.cell_output.capacity) 141 | )}(CKB) < ${t( 142 | "tutorial.widget.capacityOfCell.actualCapacity" 143 | )}:${totalByteLength}(CKB), ❌`; 144 | 145 | const ballStatusStyle = 146 | BigInt(utils.shannon2CKB(utils.hex2dec(myCell.cell_output.capacity))) > 147 | BigInt(totalByteLength) 148 | ? {} 149 | : { border: "1px solid red" }; 150 | 151 | const hrStatusStyle = 152 | BigInt(utils.shannon2CKB(utils.hex2dec(myCell.cell_output.capacity))) > 153 | BigInt(totalByteLength) 154 | ? styles.hr 155 | : { ...styles.hr, ...{ borderTop: "1px solid red" } }; 156 | 157 | return ( 158 |
    159 |
    160 | { 162 | handleInputChange(e.currentTarget.value); 163 | }} 164 | placeholder={t("tutorial.widget.capacityOfCell.inputPlaceHolder")} 165 | type="text" 166 | style={styles.input} 167 | /> 168 |
    169 |
    175 |
    182 |
    183 | {t("tutorial.widget.capacityOfCell.capacity")}
    184 |
    185 | {totalByteLength} Bytes
    186 |
    187 |
    188 | {myCell.data} 189 |
    190 |
    191 |
    192 |
    193 | {t("tutorial.widget.capacityOfCell.isCellCapacityEnough")} 194 | 198 |
    199 | 200 | 208 | 209 |
    210 | {t("tutorial.widget.capacityOfCell.cellContent")}: 211 | 215 | {t("tutorial.widget.capacityOfCell.4FieldSumCapacity")}: 216 | 227 | 228 |
    229 |
    230 |
    231 |
    232 | ); 233 | } 234 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/common/Cell.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties, useState } from "react"; 2 | import { Cell } from "../../../../types/blockchain"; 3 | import commonStyle from "../../../widget/common_style"; 4 | import { Modal, Fade } from "@material-ui/core"; 5 | import CodePiece from "../../../widget/code"; 6 | import { useDrag, DragSourceMonitor } from "react-dnd"; 7 | import { ItemTypes } from "./ItemTypes"; 8 | import { I18nComponentsProps } from "../../../../types/i18n"; 9 | 10 | const styles = { 11 | ...commonStyle, 12 | ...{ 13 | cell_panel: { 14 | width: "108px", 15 | height: "108px", 16 | listStyle: "none", 17 | float: "left" as const, 18 | marginRight: "10px", 19 | marginBottom: "10px", 20 | padding: "10px", 21 | borderRadius: "100%", 22 | }, 23 | ball: { 24 | width: "100%", 25 | height: "100%", 26 | borderRadius: "100%", 27 | border: "1px solid " + commonStyle.main_color.color, 28 | textAlign: "center" as const, 29 | justifyContent: "center" as const, 30 | fontSize: "10px", 31 | alignItems: "center" as const, 32 | cursor: "pointer", 33 | }, 34 | ball_hover: { 35 | background: "gray", 36 | }, 37 | cell_content: { 38 | margin: "30% auto", 39 | }, 40 | }, 41 | }; 42 | 43 | export interface SingleCellProps extends I18nComponentsProps { 44 | cell: Cell; 45 | key_id?: string | number; 46 | custom_style?: CSSProperties; 47 | isDraggable?: boolean; //default is draggable. 48 | } 49 | 50 | export default function SingleCell(props: SingleCellProps) { 51 | const { t, cell, key_id, isDraggable } = props; 52 | const [isHidden, setIsHidden] = useState(false); 53 | const [isHover, setIsHover] = useState(false); 54 | const hovering = () => { 55 | setIsHover(true); 56 | }; 57 | const unHover = () => { 58 | setIsHover(false); 59 | }; 60 | 61 | const [open, setOpen] = useState(false); 62 | 63 | const handleOpen = () => { 64 | setOpen(!open); 65 | }; 66 | 67 | const handleClose = () => { 68 | setOpen(!open); 69 | }; 70 | 71 | /*** 72 | * make cell draggable 73 | */ 74 | const [{ isDragging }, drag] = useDrag({ 75 | item: { cell, type: ItemTypes.CELL }, 76 | end: (item, monitor: DragSourceMonitor) => { 77 | const dropResult = monitor.getDropResult(); 78 | if (item && dropResult) { 79 | console.log( 80 | `You dropped ${item.cell.cell_output.capacity} into ${dropResult.name}!` 81 | ); 82 | setIsHidden(dropResult.isOriginHidden); 83 | } 84 | }, 85 | collect: (monitor) => ({ 86 | isDragging: monitor.isDragging(), 87 | }), 88 | }); 89 | const opacity = isDragging ? 0.4 : 1; 90 | const display = isHidden ? "none" : "inline-block"; 91 | 92 | return ( 93 |
  • 103 |
    107 |
    108 | {t("tutorial.widget.cell.capacity")}
    109 |
    110 | {cell.cell_output.capacity} 111 |
    112 |
    113 | 114 | 122 | 123 |
    124 | 128 | 129 |
    130 |
    131 |
    132 |
  • 133 | ); 134 | } 135 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/common/Cells.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties, useEffect, useState } from "react"; 2 | import Api from "../../../../api/blockchain"; 3 | import type { QueryOption, Cell } from "../../../../types/blockchain"; 4 | import SingleCell from "./Cell"; 5 | import FreshButton from "../../../widget/fresh_button"; 6 | import { I18nComponentsProps } from "../../../../types/i18n"; 7 | 8 | export interface Props extends I18nComponentsProps { 9 | query: QueryOption; 10 | length?: number; 11 | render_dep?: any; 12 | custom_style?: { 13 | btn_style?: CSSProperties; 14 | layout_style?: CSSProperties; 15 | }; 16 | text?: { 17 | title?: string; 18 | btn_text?: string; 19 | }; 20 | } 21 | 22 | const styles = { 23 | main: { 24 | textAlign: "center" as const, 25 | border: "1px solid white", 26 | clear: "both" as const, 27 | }, 28 | cell_panel: { 29 | width: "600px", 30 | border: "1px solid white", 31 | float: "left" as const, 32 | marginRight: "20px", 33 | padding: "10px", 34 | listStyleType: "none", 35 | overflow: "hidden", 36 | fontSize: "10px", 37 | display: "block", 38 | }, 39 | }; 40 | 41 | export default function Cells(props: Props) { 42 | const { t } = props; 43 | const [cells, setCells] = useState([]); 44 | const [isLoading, setIsLoading] = useState(false); 45 | 46 | const btn_style = props.custom_style?.btn_style; 47 | const layout_style = props.custom_style?.layout_style; 48 | 49 | useEffect(() => { 50 | if (props.render_dep) queryCells(); 51 | }, [props.render_dep]); 52 | 53 | async function queryCells() { 54 | setIsLoading(true); 55 | if (props.query.lock || props.query.type) { 56 | const api = new Api(); 57 | const length = props.length || 10; 58 | var myCells = await api.getLiveCells(props.query, length); 59 | setCells( 60 | myCells.map((cell: Cell, index: number) => ( 61 | 62 | )) as any 63 | ); 64 | } 65 | setIsLoading(false); 66 | } 67 | 68 | return ( 69 |
    76 |

    {props.text?.title}

    77 | 83 |
      84 | {cells.length > 0 ? ( 85 | cells 86 | ) : ( 87 |

      No Cell Found

      88 | )} 89 |
    90 |

    91 |

    92 | ); 93 | } 94 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/common/DragCellToInput.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useDrop } from "react-dnd"; 3 | import { ItemTypes } from "./ItemTypes"; 4 | import commonStyle from "../../../widget/common_style"; 5 | import { Cell, CellDep, Input, Script } from "../../../../types/blockchain"; 6 | import Api from "../../../../api/blockchain"; 7 | import utils from "../../../../utils/index"; 8 | 9 | const styles = { 10 | ...commonStyle, 11 | ...{ 12 | drop_place: { 13 | height: "200px", 14 | border: "1px solid gray", 15 | textAlign: "left" as const, 16 | padding: "0 10px", 17 | overflowY: "scroll" as const, 18 | }, 19 | }, 20 | }; 21 | 22 | export type DragItem = { 23 | cell: Cell; 24 | type: string; 25 | }; 26 | 27 | export type ChainConfig = { 28 | PREFIX: string; 29 | SCRIPTS: { 30 | SECP256K1_BLAKE160: { 31 | CODE_HASH: string; 32 | HASH_TYPE: "type" | "data"; 33 | TX_HASH: string; 34 | INDEX: string; 35 | DEP_TYPE: "dep_group" | "code"; 36 | SHORT_ID: number; 37 | }; 38 | SECP256K1_BLAKE160_MULTISIG: { 39 | CODE_HASH: string; 40 | HASH_TYPE: "type" | "data"; 41 | TX_HASH: string; 42 | INDEX: string; 43 | DEP_TYPE: "dep_group" | "code"; 44 | SHORT_ID: number; 45 | }; 46 | DAO: { 47 | CODE_HASH: string; 48 | HASH_TYPE: "type" | "data"; 49 | TX_HASH: string; 50 | INDEX: string; 51 | DEP_TYPE: "dep_group" | "code"; 52 | SHORT_ID: number; 53 | }; 54 | }; 55 | }; 56 | 57 | export type Props = { 58 | onDropShowing: ( 59 | cells: Cell[], 60 | cell_deps: CellDep[], 61 | inputs: Input[] 62 | ) => JSX.Element; 63 | onCallbackData?: ( 64 | cells: Cell[], 65 | cell_deps: CellDep[], 66 | inputs: Input[] 67 | ) => void; 68 | }; 69 | 70 | export default function DragCellToInput(props: Props) { 71 | const [config, setConfig] = useState(); 72 | 73 | const [cells, setCells] = useState([]); 74 | const [cell_deps, setCellDeps] = useState([]); 75 | const [inputs, setInputs] = useState([]); 76 | 77 | // make place droppable 78 | const [{ canDrop, isOver }, drop] = useDrop({ 79 | accept: ItemTypes.CELL, 80 | drop(item: DragItem, monitor) { 81 | try { 82 | prepareJsonData(item.cell); 83 | return { name: "tx-input(json)", isOriginHidden: true }; 84 | } catch (error: any) { 85 | alert(error); 86 | return { name: "tx-input(json)", isOriginHidden: false }; 87 | } 88 | }, 89 | collect: (monitor) => ({ 90 | isOver: monitor.isOver(), 91 | canDrop: monitor.canDrop(), 92 | }), 93 | }); 94 | 95 | const prepareJsonData = (cell: Cell) => { 96 | if (cell.out_point) { 97 | inputs.push({ 98 | previous_output: cell.out_point, 99 | since: "0x0", 100 | }); 101 | } else { 102 | // todo: should try another way to get outpoint. 103 | throw new Error("failed to add input cell! can not find outpoint."); 104 | } 105 | 106 | const recognize_dep = getCellDepByScript(cell.cell_output.lock); 107 | if ( 108 | recognize_dep !== "un-recognize cell dep" && 109 | typeof recognize_dep !== "string" 110 | ) { 111 | if (!utils.isObjectInArray(recognize_dep, cell_deps)) 112 | cell_deps.push(recognize_dep); 113 | } 114 | 115 | cells.push(cell); 116 | }; 117 | 118 | const getCellDepByScript = (script: Script): CellDep | string => { 119 | const scripts = config?.SCRIPTS; 120 | const secp160 = scripts?.SECP256K1_BLAKE160; 121 | const secp160Multi = scripts?.SECP256K1_BLAKE160_MULTISIG; 122 | const dao = scripts?.DAO; 123 | 124 | switch (script.code_hash) { 125 | case secp160?.CODE_HASH: 126 | if (script.hash_type === secp160?.HASH_TYPE) { 127 | return { 128 | out_point: { 129 | tx_hash: secp160.TX_HASH, 130 | index: secp160.INDEX, 131 | }, 132 | dep_type: secp160.DEP_TYPE, 133 | }; 134 | } 135 | break; 136 | 137 | case secp160Multi?.CODE_HASH: 138 | if (script.hash_type === secp160Multi?.HASH_TYPE) { 139 | return { 140 | out_point: { 141 | tx_hash: secp160Multi.TX_HASH, 142 | index: secp160Multi.INDEX, 143 | }, 144 | dep_type: secp160Multi.DEP_TYPE, 145 | }; 146 | } 147 | break; 148 | 149 | case dao?.CODE_HASH: 150 | if (script.hash_type === dao?.HASH_TYPE) { 151 | return { 152 | out_point: { 153 | tx_hash: dao.TX_HASH, 154 | index: dao.INDEX, 155 | }, 156 | dep_type: dao.DEP_TYPE, 157 | }; 158 | } 159 | break; 160 | 161 | default: 162 | break; 163 | } 164 | 165 | return "un-recognize cell dep"; 166 | }; 167 | 168 | async function fetchChainConfig() { 169 | const api = new Api(); 170 | var config: ChainConfig = await api.getChainConfig(); 171 | setConfig(config); 172 | } 173 | 174 | useEffect(() => { 175 | fetchChainConfig(); 176 | }, []); 177 | 178 | const isActive = canDrop && isOver; 179 | const border = isActive 180 | ? "1px solid " + commonStyle.main_color.color 181 | : "1px solid gray"; 182 | 183 | return ( 184 |
    185 | {props.onDropShowing(cells, cell_deps, inputs)} 186 |
    187 | ); 188 | } 189 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/common/DragCellToInputBall.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useDrop } from "react-dnd"; 3 | import { ItemTypes } from "./ItemTypes"; 4 | import commonStyle from "../../../widget/common_style"; 5 | import { Cell, CellDep, Input, Script } from "../../../../types/blockchain"; 6 | import Api from "../../../../api/blockchain"; 7 | import utils from "../../../../utils/index"; 8 | import SingleCell from "./Cell"; 9 | import { I18nComponentsProps } from "../../../../types/i18n"; 10 | import { t } from "i18next"; 11 | 12 | const styles = { 13 | ...commonStyle, 14 | ...{ 15 | drop_place: { 16 | height: "200px", 17 | border: "1px solid gray", 18 | textAlign: "left" as const, 19 | overflowY: "scroll" as const, 20 | }, 21 | header: { 22 | height: "20%", 23 | fontSize: "16px", 24 | textAlign: "center" as const, 25 | borderBottom: "1px solid gray", 26 | }, 27 | hint_text: { 28 | marginTop: "10px", 29 | display: "inline-block" as const, 30 | }, 31 | }, 32 | }; 33 | 34 | export type DragItem = { 35 | cell: Cell; 36 | type: string; 37 | }; 38 | 39 | export type ChainConfig = { 40 | PREFIX: string; 41 | SCRIPTS: { 42 | SECP256K1_BLAKE160: { 43 | CODE_HASH: string; 44 | HASH_TYPE: "type" | "data"; 45 | TX_HASH: string; 46 | INDEX: string; 47 | DEP_TYPE: "dep_group" | "code"; 48 | SHORT_ID: number; 49 | }; 50 | SECP256K1_BLAKE160_MULTISIG: { 51 | CODE_HASH: string; 52 | HASH_TYPE: "type" | "data"; 53 | TX_HASH: string; 54 | INDEX: string; 55 | DEP_TYPE: "dep_group" | "code"; 56 | SHORT_ID: number; 57 | }; 58 | DAO: { 59 | CODE_HASH: string; 60 | HASH_TYPE: "type" | "data"; 61 | TX_HASH: string; 62 | INDEX: string; 63 | DEP_TYPE: "dep_group" | "code"; 64 | SHORT_ID: number; 65 | }; 66 | }; 67 | }; 68 | 69 | export interface Props extends I18nComponentsProps { 70 | get_contents?: (cells: Cell[], cell_deps: CellDep[], inputs: Input[]) => void; 71 | onClearCall?: boolean; 72 | makeOriginCellHidden?: boolean; 73 | } 74 | 75 | export default function DragCellToInputBall(props: Props) { 76 | const { get_contents, onClearCall, makeOriginCellHidden } = props; 77 | 78 | const [config, setConfig] = useState(); 79 | 80 | const [cells, setCells] = useState([]); 81 | const [cell_deps, setCellDeps] = useState([]); 82 | const [inputs, setInputs] = useState([]); 83 | 84 | // make place droppable 85 | const [{ canDrop, isOver }, drop] = useDrop({ 86 | accept: ItemTypes.CELL, 87 | drop(item: DragItem, monitor) { 88 | try { 89 | prepareJsonData(item.cell); 90 | const isOriginHidden = 91 | makeOriginCellHidden !== undefined ? makeOriginCellHidden : true; // default mode is hidden origin after success drag. 92 | return { name: "tx-input(json)", isOriginHidden: isOriginHidden }; 93 | } catch (error: any) { 94 | alert(error); 95 | return { name: "tx-input(json)", isOriginHidden: false }; 96 | } 97 | }, 98 | collect: (monitor) => ({ 99 | isOver: monitor.isOver(), 100 | canDrop: monitor.canDrop(), 101 | }), 102 | }); 103 | 104 | const prepareJsonData = (cell: Cell) => { 105 | if (cell.out_point) { 106 | const input_cell = { 107 | previous_output: cell.out_point, 108 | since: "0x0", 109 | }; 110 | if (!utils.isObjectInArray(input_cell, inputs)) { 111 | inputs.push(input_cell); 112 | 113 | //make canvas display the same cell 114 | cells.push(cell); 115 | } else { 116 | throw new Error("cell already exits!"); 117 | } 118 | } else { 119 | // todo: should try another way to get outpoint. 120 | throw new Error("failed to add input cell! can not find outpoint."); 121 | } 122 | 123 | const recognize_dep = getCellDepByScript(cell.cell_output.lock); 124 | if ( 125 | recognize_dep !== "un-recognize cell dep" && 126 | typeof recognize_dep !== "string" 127 | ) { 128 | if (!utils.isObjectInArray(recognize_dep, cell_deps)) 129 | cell_deps.push(recognize_dep); 130 | } 131 | 132 | if (get_contents) { 133 | get_contents(cells, cell_deps, inputs); 134 | } 135 | }; 136 | 137 | const getCellDepByScript = (script: Script): CellDep | string => { 138 | const scripts = config?.SCRIPTS; 139 | const secp160 = scripts?.SECP256K1_BLAKE160; 140 | const secp160Multi = scripts?.SECP256K1_BLAKE160_MULTISIG; 141 | const dao = scripts?.DAO; 142 | 143 | switch (script.code_hash) { 144 | case secp160?.CODE_HASH: 145 | if (script.hash_type === secp160?.HASH_TYPE) { 146 | return { 147 | out_point: { 148 | tx_hash: secp160.TX_HASH, 149 | index: secp160.INDEX, 150 | }, 151 | dep_type: secp160.DEP_TYPE, 152 | }; 153 | } 154 | break; 155 | 156 | case secp160Multi?.CODE_HASH: 157 | if (script.hash_type === secp160Multi?.HASH_TYPE) { 158 | return { 159 | out_point: { 160 | tx_hash: secp160Multi.TX_HASH, 161 | index: secp160Multi.INDEX, 162 | }, 163 | dep_type: secp160Multi.DEP_TYPE, 164 | }; 165 | } 166 | break; 167 | 168 | case dao?.CODE_HASH: 169 | if (script.hash_type === dao?.HASH_TYPE) { 170 | return { 171 | out_point: { 172 | tx_hash: dao.TX_HASH, 173 | index: dao.INDEX, 174 | }, 175 | dep_type: dao.DEP_TYPE, 176 | }; 177 | } 178 | break; 179 | 180 | default: 181 | break; 182 | } 183 | 184 | return "un-recognize cell dep"; 185 | }; 186 | 187 | const clear = () => { 188 | setCells([]); 189 | setCellDeps([]); 190 | setInputs([]); 191 | }; 192 | 193 | async function fetchChainConfig() { 194 | const api = new Api(); 195 | var config: ChainConfig = await api.getChainConfig(); 196 | setConfig(config); 197 | } 198 | 199 | useEffect(() => { 200 | if (onClearCall) clear(); 201 | }, [onClearCall]); 202 | 203 | useEffect(() => { 204 | fetchChainConfig(); 205 | }, []); 206 | 207 | const isActive = canDrop && isOver; 208 | const border = isActive 209 | ? "1px solid " + commonStyle.main_color.color 210 | : "1px solid gray"; 211 | const color = isActive ? commonStyle.main_color.color : "white"; 212 | const borderBottom = isActive 213 | ? "1px solid " + commonStyle.main_color.color 214 | : "1px solid gray"; 215 | return ( 216 |
    217 |
    218 | 219 | {t("tutorial.widget.dragCellToInputBall.title")} 220 | 221 |
    222 | {cells.map((cell: Cell, index: number) => ( 223 | 230 | ))} 231 |
    232 | ); 233 | } 234 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/common/DragCellToInputJson.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Cell, CellDep, Input } from "../../../../types/blockchain"; 3 | import CodePiece from "../../../widget/code"; 4 | import DragCellToInput from "./DragCellToInput"; 5 | 6 | export default function DragCellToInputJson() { 7 | const generateInputJson = ( 8 | cells: Cell[], 9 | cell_deps: CellDep[], 10 | inputs: Input[] 11 | ) => { 12 | const data = { 13 | cell_deps: cell_deps, 14 | inputs: inputs, 15 | }; 16 | return ( 17 | 21 | ); 22 | }; 23 | 24 | return ; 25 | } 26 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/common/ItemTypes.ts: -------------------------------------------------------------------------------- 1 | export const ItemTypes = { 2 | CELL: "cell", 3 | }; 4 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/common/OutputCreator.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Cell, TxOutput } from "../../../../types/blockchain"; 3 | import { I18nComponentsProps } from "../../../../types/i18n"; 4 | import TotalCapacity from "./TotalCapacity"; 5 | 6 | export interface OutputCreatorProps extends I18nComponentsProps { 7 | input_cells: Cell[]; 8 | get_tx_output?: (txo: TxOutput) => void; 9 | onClearCall?: boolean; 10 | } 11 | 12 | export default function OutputCreator(props: OutputCreatorProps) { 13 | const { t, input_cells, get_tx_output, onClearCall } = props; 14 | return ( 15 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/common/TotalCapacity.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Cell, TxOutput } from "../../../../types/blockchain"; 3 | import commonStyle from "../../../widget/common_style"; 4 | import FreshButton from "../../../widget/fresh_button"; 5 | import SingleCell from "./Cell"; 6 | import utils from "../../../../utils/index"; 7 | import { Modal, Fade } from "@material-ui/core"; 8 | import EditOutputCells from "./EditOutputCells"; 9 | import { I18nComponentsProps } from "../../../../types/i18n"; 10 | 11 | const styles = { 12 | ...commonStyle, 13 | ...{ 14 | root: { 15 | width: "100%", 16 | border: "1px solid gray", 17 | height: "200px", 18 | }, 19 | capacity: { 20 | width: "100%", 21 | height: "80%", 22 | paddingTop: "5px", 23 | }, 24 | capacity_header: { 25 | height: "20%", 26 | overflowY: "hidden" as const, 27 | }, 28 | header_number: { 29 | width: "17%", 30 | overflowX: "scroll" as const, 31 | display: "inline-block" as const, 32 | verticalAlign: "text-bottom" as const, 33 | background: "white", 34 | color: "black", 35 | padding: "0 5px", 36 | marginRight: "5px", 37 | }, 38 | output_cell: { 39 | textAlign: "center" as const, 40 | height: "80%", 41 | overflowY: "scroll" as const, 42 | background: "white", 43 | color: "black", 44 | }, 45 | fee: { 46 | position: "relative" as const, 47 | bottom: "0px", 48 | width: "100%", 49 | height: "15%", 50 | borderTop: "1px solid gray", 51 | paddingTop: "5px", 52 | background: "black", 53 | fontSize: "16px", 54 | }, 55 | }, 56 | }; 57 | 58 | export interface TotalCapacityProps extends I18nComponentsProps { 59 | cells: Cell[]; 60 | get_tx_output?: (txo: TxOutput) => void; 61 | onClearCall?: boolean; 62 | } 63 | 64 | const calculateCellCapacity = (cells: Cell[]) => { 65 | var total = BigInt(0); 66 | cells.map((cell) => { 67 | total = total + BigInt(cell.cell_output.capacity); 68 | }); 69 | return "0x" + total.toString(16); 70 | }; 71 | 72 | export default function TotalCapacity(props: TotalCapacityProps) { 73 | const { t, cells, get_tx_output, onClearCall } = props; 74 | const [capacity, setCapacity] = useState("0x0"); 75 | const [fee, setFee] = useState("0"); 76 | const [myCells, setMyCells] = useState([]); 77 | 78 | const [isModalOpen, setIsModalOpen] = useState(false); 79 | const handleModalOpen = () => { 80 | setIsModalOpen(true); 81 | }; 82 | const handleModalClose = () => { 83 | setIsModalOpen(false); 84 | }; 85 | 86 | const updateMyCells = (cells: Cell[]) => { 87 | setMyCells(cells); 88 | }; 89 | 90 | const default_one_cell = async (cells: Cell[]) => { 91 | let sum = await calculateCellCapacity(cells); 92 | let c: Cell = { 93 | cell_output: { 94 | capacity: sum, 95 | lock: { 96 | code_hash: 97 | "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 98 | hash_type: "type", 99 | args: "", 100 | }, 101 | }, 102 | data: "0x", 103 | }; 104 | setMyCells([c]); 105 | }; 106 | 107 | useEffect(() => { 108 | if (cells.length !== 0) { 109 | default_one_cell(cells); 110 | updateCapacity(cells); 111 | } else { 112 | updateCapacity(cells); //update the capacity when calling clear from parent componet 113 | } 114 | }, [cells]); 115 | 116 | useEffect(() => { 117 | setFee(calculate_fee()); 118 | if (get_tx_output) { 119 | const tx_output = calculateTxOutPut(); 120 | get_tx_output(tx_output); 121 | } 122 | }, [myCells]); 123 | 124 | useEffect(() => { 125 | if (onClearCall) setMyCells([]); 126 | }, [onClearCall]); 127 | 128 | const updateCapacity = async (cells: Cell[]) => { 129 | let sum = calculateCellCapacity(cells); 130 | await setCapacity(sum); 131 | }; 132 | 133 | const calculateTxOutPut = (): TxOutput => { 134 | const outputs = myCells.map((cell) => { 135 | return { 136 | capacity: cell.cell_output.capacity, 137 | lock: cell.cell_output.lock, 138 | }; 139 | }); 140 | const output_datas = myCells.map((cell) => cell.data); 141 | return { 142 | outputs: outputs, 143 | outputs_data: output_datas, 144 | }; 145 | }; 146 | 147 | const distribute_cells = () => { 148 | handleModalOpen(); 149 | }; 150 | 151 | const calculate_fee = () => { 152 | let sum = calculateCellCapacity(myCells); 153 | const fee = ( 154 | BigInt(utils.hex2dec(capacity)) - BigInt(utils.hex2dec(sum)) 155 | ).toString(16); 156 | console.log(capacity, sum, fee); 157 | return utils.shannon2CKB(utils.hex2dec("0x" + fee)); 158 | }; 159 | 160 | const isFeeOk = BigInt(fee) > BigInt("0") ? true : false; 161 | 162 | return ( 163 |
    164 |
    165 |
    166 |
    167 | {t("tutorial.widget.outputCreatorTotalCapacity.totalCapText")} 168 |
    169 |
    170 | {utils.shannon2CKB(utils.hex2dec(capacity))} 171 |
    172 |
    CKB |
    173 | 185 |
    186 |
    187 | {myCells.map((cell, index) => ( 188 | 194 | ))} 195 |
    196 |
    197 |
    198 | {t("tutorial.widget.outputCreatorTotalCapacity.minerFee")} {fee} CKB |{" "} 199 | {isFeeOk ? "✅" : "❌"} 200 |
    201 | 202 | 211 | 212 |
    213 | 219 |
    220 |
    221 |
    222 |
    223 |
    224 | ); 225 | } 226 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/common/Tx.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import type { Transaction } from "../../../../types/blockchain"; 3 | import { Modal, Backdrop, Fade } from "@material-ui/core"; 4 | import CodePiece from "../../../widget/code"; 5 | 6 | export type TxProps = { 7 | tx: Transaction; 8 | key_id?: number | string; 9 | }; 10 | 11 | const styles = { 12 | box_content_link: { 13 | cursor: "pointer", 14 | listStyleType: "none", 15 | marginLeft: "0", 16 | marginBottom: "10px", 17 | }, 18 | modal: { 19 | maxWidth: "700px", 20 | overflowY: "scroll" as const, 21 | height: "90%", 22 | display: "block", 23 | alignItems: "center", 24 | justifyContent: "center", 25 | margin: "2em auto", 26 | }, 27 | paper: { 28 | backgroundColor: "gray", 29 | border: "2px solid #000", 30 | boxShadow: "10px", 31 | padding: "10px", 32 | outline: "none", 33 | }, 34 | }; 35 | 36 | export default function ShowTxInfo(props: TxProps) { 37 | const { tx, key_id } = props; 38 | const [open, setOpen] = useState(false); 39 | 40 | const handleOpen = () => { 41 | setOpen(!open); 42 | }; 43 | 44 | const handleClose = () => { 45 | setOpen(!open); 46 | }; 47 | 48 | return ( 49 |
  • 54 | {tx.hash?.slice(0, 12)}.. 55 | 68 | 69 |
    70 | 74 | 75 |
    76 |
    77 |
    78 |
  • 79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/common/TxConstructor.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | import commonStyle from "../../../widget/common_style"; 3 | import { Grid } from "@material-ui/core"; 4 | import DragCell2InputBall from "./DragCellToInputBall"; 5 | import OutputCreator from "./OutputCreator"; 6 | import FreshButton from "../../../widget/fresh_button"; 7 | import CodePiece from "../../../widget/code"; 8 | import { 9 | Cell, 10 | Input, 11 | CellDep, 12 | RawTransaction, 13 | TxOutput, 14 | } from "../../../../types/blockchain"; 15 | import NeonText from "../../../widget/neon_text"; 16 | import { I18nComponentsProps } from "../../../../types/i18n"; 17 | 18 | const styles = { 19 | ...commonStyle, 20 | ...{ 21 | root: { 22 | marginTop: "1em", 23 | }, 24 | input_box: { 25 | textAlign: "center" as const, 26 | }, 27 | output_box: { 28 | textAlign: "center" as const, 29 | }, 30 | covert_label: { 31 | textAlign: "center" as const, 32 | fontSize: "50px", 33 | display: "flex", 34 | verticalAlign: "middle", 35 | justifyContent: "center", 36 | alignItems: "center", 37 | }, 38 | json_result: { 39 | width: "100%", 40 | minHeight: "200px", 41 | border: "1px solid gray", 42 | marginTop: "10px", 43 | }, 44 | subtitle: { 45 | width: "100%", 46 | }, 47 | neon_text_style: { 48 | brand_style: { 49 | width: "fit-content" as const, 50 | margin: "0 auto", 51 | padding: "5px", 52 | borderBottom: "0", 53 | }, 54 | }, 55 | }, 56 | }; 57 | 58 | export default function TxConstructor(props: I18nComponentsProps) { 59 | const { t } = props; 60 | const [input_cells, setInputCells] = useState([]); 61 | const [input_cell_deps, setInputCellDeps] = useState([]); 62 | const [input_cell_inputs, setInputCellInputs] = useState([]); 63 | const [tx_output, setTxOutput] = useState(); 64 | const [raw_tx, setRawTx] = useState(); 65 | 66 | const [isClear, setIsClear] = useState(false); 67 | 68 | const generateJSON = () => { 69 | const data: RawTransaction = { 70 | ...{ 71 | version: "0x0", 72 | header_deps: [], 73 | cell_deps: input_cell_deps, 74 | inputs: input_cell_inputs, 75 | outputs: [], 76 | outputs_data: [], 77 | witnesses: Array(input_cell_inputs.length).fill("0x"), 78 | }, 79 | ...tx_output, 80 | }; 81 | setRawTx(data); 82 | }; 83 | 84 | const clearAll = () => { 85 | setIsClear(true); 86 | setInputCells([]); 87 | setTxOutput({ outputs: [], outputs_data: [] }); 88 | setRawTx(undefined); 89 | 90 | setTimeout(() => { 91 | setIsClear(false); 92 | }, 1000); 93 | }; 94 | 95 | const handleInputCellChange = ( 96 | cells: Cell[], 97 | cell_deps: CellDep[], 98 | inputs: Input[] 99 | ) => { 100 | setInputCells(cells.map((cell) => cell)); // todo: why have to init a new instance? if not, won't work. 101 | setInputCellDeps(cell_deps.map((c) => c)); 102 | setInputCellInputs(inputs.map((i) => i)); 103 | }; 104 | 105 | const handleOutputChange = (tx_output: TxOutput) => { 106 | setTxOutput(tx_output); 107 | }; 108 | 109 | return ( 110 |
    111 |
    112 | 113 | 114 |
    115 |
    116 | 120 |
    121 | 127 |
    128 |
    129 | 130 | 131 | 132 | 133 |
    134 |
    135 | 139 |
    140 | 146 |
    147 |
    148 |
    149 | 150 | 151 | 160 | 161 | 162 | 171 | 172 | 173 | 174 | 175 |
    176 | 177 |
    178 |
    179 |
    180 |
    181 | ); 182 | } 183 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/common/Txs.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Transaction, 4 | TransactionWithStatus, 5 | } from "../../../../types/blockchain"; 6 | import Tx from "./Tx"; 7 | 8 | const styles = { 9 | panel: { 10 | padding: "0", 11 | margin: "0", 12 | width: "100%", 13 | overflowX: "hidden" as const, 14 | }, 15 | }; 16 | 17 | export type TxsProps = { 18 | txs: Transaction[] | TransactionWithStatus[]; 19 | }; 20 | 21 | export default function Txs(props: TxsProps) { 22 | const { txs } = props; 23 | 24 | const is_TransactionWithStatus_type = ( 25 | toBeDetermined: Transaction | TransactionWithStatus 26 | ): toBeDetermined is TransactionWithStatus => { 27 | if ((toBeDetermined as TransactionWithStatus).transaction !== undefined) { 28 | return true; 29 | } else { 30 | return false; 31 | } 32 | }; 33 | 34 | const mytxs = (txs as Array).map( 35 | ( 36 | tx: Transaction | TransactionWithStatus, 37 | index: string | number | undefined 38 | ) => { 39 | if (is_TransactionWithStatus_type(tx)) { 40 | return ; 41 | } else { 42 | return ; 43 | } 44 | } 45 | ); 46 | return ( 47 |
      48 | {mytxs.length > 0 ? ( 49 | mytxs 50 | ) : ( 51 |

      No Transactions Found

      52 | )} 53 |
    54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/common/Wallets.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, CSSProperties } from "react"; 2 | import Api from "../../../../api/blockchain"; 3 | import type { Wallet } from "../../../../types/blockchain"; 4 | import commonStyles from "../../../widget/common_style"; 5 | import CopyText from "../../../widget/copy_text"; 6 | import CodePiece from "../../../widget/code"; 7 | import { Typography, Container } from "@material-ui/core"; 8 | import { I18nComponentsProps } from "../../../../types/i18n"; 9 | 10 | const wallet_arcii = ` 11 | ___________________________________ 12 | |#######====================#######| 13 | |#(1)* BANK of CKB * #| 14 | |#** /===\\ ******** **#| 15 | |*# {G} | (") | #*| 16 | |#* ****** | /v\\ | O N E *#| 17 | |#(1) \\===/ (1)#| 18 | |##=========ONE WALLET===========##| 19 | ------------------------------------ 20 | `; 21 | const wallet_arcii_2 = ` 22 | +----------+ 23 | | | 24 | | CKB | 25 | | Wallet | 26 | | | 27 | +----------+ 28 | `; 29 | const wallet_bottom = ` 30 | + + 31 | +-------------------------------+ 32 | `; 33 | const wallet_top = ` 34 | +-------------------------------+ 35 | + + 36 | `; 37 | 38 | const styles = { 39 | ...commonStyles, 40 | ...{ 41 | wallet_section: { 42 | border: "1px solid", 43 | marginTop: "2em", 44 | marginBottom: "2em", 45 | }, 46 | wallets: { 47 | marginTop: "20px", 48 | marginBottom: "20px", 49 | }, 50 | wallet_panel: { 51 | maxWidth: "300px", 52 | float: "left" as const, 53 | marginRight: "20px", 54 | padding: "10px", 55 | listStyleType: "none", 56 | overflow: "hidden", 57 | fontSize: "10px", 58 | display: "block", 59 | textAlign: "center" as const, 60 | marginBottom: "5px", 61 | }, 62 | alert_text: { 63 | color: "red", 64 | fontSize: "12px", 65 | textAlign: "center" as const, 66 | }, 67 | wallet_info: { 68 | textAlign: "center" as const, 69 | }, 70 | wallet_info_text: { 71 | fontSize: "12px", 72 | }, 73 | }, 74 | }; 75 | 76 | export type WalletInfoProps = { 77 | wallet: Wallet; 78 | }; 79 | 80 | export function WalletInfo(props: WalletInfoProps) { 81 | const { wallet } = props; 82 | const [isShowing, setIsShowing] = useState(false); 83 | 84 | const toggle = () => { 85 | setIsShowing(!isShowing); 86 | }; 87 | 88 | const info = () => { 89 | if (isShowing) { 90 | return ( 91 |
    92 | 101 |
    102 |

    103 | 104 | mainet: 105 |            106 | 107 | {wallet.mainnet.slice(0, 8)}.. 108 | {wallet.mainnet.slice(wallet.mainnet.length - 5)}{" "} 109 | 110 |

    111 |

    112 | 113 | testnet: 114 |            115 | 116 | {wallet.testnet.slice(0, 8)}.. 117 | {wallet.testnet.slice(wallet.testnet.length - 5)}{" "} 118 | 119 |

    120 |

    121 | 122 | lock_arg:       123 | 124 | {wallet.lock_arg.slice(0, 8)}.. 125 | {wallet.lock_arg.slice(wallet.lock_arg.length - 5)}{" "} 126 | 127 |

    128 |

    129 | private_key: 130 | {wallet.private_key.slice(0, 8)}.. 131 | {wallet.private_key.slice(wallet.private_key.length - 5)}{" "} 132 | 133 |

    134 |
    135 | 144 |
    145 | ); 146 | } else { 147 | return ( 148 | 152 | ); 153 | } 154 | }; 155 | return ( 156 |
    157 | {info()} 158 |
    159 | ); 160 | } 161 | 162 | export interface Props extends I18nComponentsProps { 163 | wallet_id?: number; 164 | custom_style?: CSSProperties; 165 | onFetchWallets?: (wallets: Wallet[]) => void; 166 | } 167 | 168 | export default function Wallets(props: Props) { 169 | const { t, custom_style } = props; 170 | 171 | const [wallets, setWallets] = useState([]); 172 | 173 | useEffect(() => { 174 | fetchWallets(); 175 | }, []); 176 | 177 | async function fetchWallets() { 178 | const api = new Api(); 179 | const myWallets = await api.getWallets(); 180 | setWallets( 181 | myWallets.map((wallet: Wallet, index: number) => { 182 | return ( 183 |
  • 184 | 185 | {" "} 186 | {t("tutorial.widget.wallets.wallet")} {index + 1}{" "} 187 | 188 | 189 |
  • 190 | ); 191 | }) 192 | ); 193 | if (props.onFetchWallets) props.onFetchWallets(myWallets); 194 | } 195 | 196 | return ( 197 | 204 |
    205 |

    206 | ☠️   {t("tutorial.widget.wallets.securityAlertMsg")} 207 |

    208 |
    209 |
    210 | {props.wallet_id ? wallets[props.wallet_id - 1] : wallets} 211 |
    212 |

    213 |
    214 | 215 | ); 216 | } 217 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/common/callBacks.ts: -------------------------------------------------------------------------------- 1 | import { notify } from "../../../widget/notify"; 2 | 3 | export const errNotifyCallBack = (err: any) => { 4 | notify(err.message); 5 | }; 6 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/show_chain_info/Balance.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, CSSProperties } from "react"; 2 | import Api from "../../../../api/blockchain"; 3 | import { HexString } from "../../../../types/blockchain"; 4 | import FreshButton from "../../../widget/fresh_button"; 5 | import commonStyles from "../../../widget/common_style"; 6 | 7 | const styles = { 8 | ...commonStyles, 9 | ...{ 10 | list_panel: { 11 | textAlign: "center" as const, 12 | border: "1px solid white", 13 | }, 14 | tx_panel: { 15 | width: "600px", 16 | border: "1px solid white", 17 | float: "left" as const, 18 | marginRight: "20px", 19 | padding: "10px", 20 | listStyleType: "none", 21 | overflow: "scroll", 22 | fontSize: "10px", 23 | display: "block", 24 | textAlign: "left" as const, 25 | }, 26 | }, 27 | }; 28 | 29 | export type BalanceProps = { 30 | lock_args: HexString; 31 | render_dep?: any; 32 | text?: { 33 | title?: string; 34 | btn_text?: string; 35 | }; 36 | custom_style?: { 37 | btn_style?: CSSProperties; 38 | layout_style?: CSSProperties; 39 | }; 40 | }; 41 | 42 | export default function Balance(props: BalanceProps) { 43 | const [balance, setBalance] = useState(); 44 | const [isLoading, setIsLoading] = useState(false); 45 | 46 | async function fetchBalance() { 47 | setIsLoading(true); 48 | const api = new Api(); 49 | var bal = await api.getBalance(props.lock_args); 50 | setBalance(bal); 51 | setIsLoading(false); 52 | } 53 | 54 | useEffect(() => { 55 | if (props.render_dep) fetchBalance(); 56 | }, [props.render_dep]); 57 | 58 | const btn_style = props.custom_style?.btn_style; 59 | const layout_style = props.custom_style?.layout_style; 60 | const title = props.text?.title; 61 | const btn_text = props.text?.btn_text; 62 | 63 | return ( 64 |

    71 | 77 |

    78 | {title}:{balance} CKB{" "} 79 |

    80 |
    81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/show_chain_info/ChainConfig.tsx: -------------------------------------------------------------------------------- 1 | import { CSSProperties } from "@material-ui/core/styles/withStyles"; 2 | import React, { useState, useEffect } from "react"; 3 | import Api from "../../../../api/blockchain"; 4 | import { ChainConfig as TypeChainConfig } from "../../../../types/blockchain"; 5 | import { I18nComponentsProps } from "../../../../types/i18n"; 6 | import CodePiece from "../../../widget/code"; 7 | 8 | const styles = { 9 | root: { 10 | textAlign: "center" as const, 11 | }, 12 | config_panel: { 13 | textAlign: "left" as const, 14 | margin: "0 auto", 15 | padding: "0 10px", 16 | border: "1px solid gray", 17 | }, 18 | }; 19 | 20 | export interface ChainConfigProps extends I18nComponentsProps { 21 | custom_style?: CSSProperties; 22 | } 23 | 24 | export default function ChainConfig(props: ChainConfigProps) { 25 | const { t, custom_style } = props; 26 | 27 | const [config, setConfig] = useState(); 28 | 29 | useEffect(() => { 30 | fetchChainConfig(); 31 | }, []); 32 | 33 | async function fetchChainConfig() { 34 | const api = new Api(); 35 | var config = await api.getChainConfig(); 36 | setConfig(config); 37 | } 38 | 39 | return ( 40 |
    41 |

    {t("tutorial.widget.showChainInfo.title")}

    42 |
    49 | 53 |
    54 |
    55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /src/components/tutorial/sections/show_chain_info/WalletCells.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from "react"; 2 | import Cells from "../common/Cells"; 3 | import WalletTxs from "./WalletTransaction"; 4 | import Balance from "./Balance"; 5 | import type { Wallet } from "../../../../types/blockchain"; 6 | import Select, { ActionMeta, OptionTypeBase, ValueType } from "react-select"; 7 | import { I18nComponentsProps } from "../../../../types/i18n"; 8 | 9 | export interface WalletCellsProps extends I18nComponentsProps { 10 | wallets: Wallet[]; 11 | } 12 | 13 | export type SelectWallet = { 14 | value: string; 15 | label: string; 16 | }; 17 | 18 | const styles = { 19 | selection_area: { 20 | color: "black", 21 | }, 22 | hidden_btn: { 23 | width: "100%", 24 | border: "0", 25 | backgroundColor: "rgb(0,0,0, 0)", 26 | height: "0", 27 | cursor: "auto", 28 | }, 29 | }; 30 | 31 | /** 32 | * todo: add balance here. 33 | */ 34 | export default function WalletCells(props: WalletCellsProps) { 35 | const { t } = props; 36 | const [wallets, setWallets] = useState([]); 37 | 38 | useEffect(() => { 39 | setWallets(props.wallets); 40 | }, [props.wallets]); 41 | 42 | const [selectedWallet, setSelectedWallet] = useState(); 43 | const options = wallets.map((w, index) => { 44 | return { 45 | value: w.lock_arg, 46 | label: 47 | t("tutorial.widget.walletCells.selectOptionLabel") + 48 | (index + 1) + 49 | ": " + 50 | w.lock_arg, 51 | }; 52 | }); 53 | 54 | const handlerSelectWallet = ( 55 | value: ValueType< 56 | { 57 | value: string; 58 | label: string; 59 | }, 60 | false 61 | >, 62 | actionMeta: ActionMeta<{ 63 | value: string; 64 | label: string; 65 | }> 66 | ) => { 67 | setSelectedWallet(value?.value); 68 | }; 69 | 70 | return ( 71 |
    72 |
    73 |