├── .eslintrc.js
├── .gitignore
├── LICENSE
├── README.md
├── index.html
├── package.json
├── postcss.config.js
├── src
├── App.less
├── App.tsx
├── api.tsx
├── assets
│ └── images
│ │ ├── bg.svg
│ │ ├── full_logo.svg
│ │ ├── img-1.svg
│ │ ├── img-10.svg
│ │ ├── img-2.svg
│ │ ├── img-3.svg
│ │ ├── img-4.svg
│ │ ├── img-5.svg
│ │ ├── img-6.svg
│ │ ├── img-7.svg
│ │ ├── img-8.svg
│ │ ├── img-9.svg
│ │ └── logo.svg
├── chia-util.tsx
├── components
│ ├── Donate.less
│ ├── Donate.tsx
│ ├── Login.tsx
│ ├── TokenSelect.tsx
│ └── UserInfo.tsx
├── favicon.svg
├── index.css
├── logo.svg
├── main.tsx
└── vite-env.d.ts
├── tailwind.config.js
├── tsconfig.json
├── vite.config.ts
└── yarn.lock
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2021: true,
5 | },
6 | extends: [
7 | 'eslint:recommended',
8 | 'plugin:react/recommended',
9 | 'plugin:@typescript-eslint/recommended',
10 | 'plugin:react/jsx-runtime',
11 | ],
12 | parser: '@typescript-eslint/parser',
13 | parserOptions: {
14 | ecmaFeatures: {
15 | jsx: true,
16 | },
17 | ecmaVersion: 12,
18 | sourceType: 'module',
19 | },
20 | plugins: ['react', '@typescript-eslint'],
21 | rules: {},
22 | }
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
6 | *.log
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 GobyWallet
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # donation site
2 |
3 | A simple donation site to show how to interact with Goby.
4 |
5 | ## Quickstart
6 |
7 | ```
8 | yarn install
9 |
10 | # preview
11 | yarn run dev
12 |
13 | # build
14 | yarn run build
15 | ```
16 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
12 | Goby
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gobywallet-demo",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "tsc && vite build",
7 | "preview": "vite preview"
8 | },
9 | "dependencies": {
10 | "antd": "^4.18.3",
11 | "autoprefixer": "^10.4.2",
12 | "axios": "^0.25.0",
13 | "bech32": "^2.0.0",
14 | "bignumber.js": "^9.0.2",
15 | "buffer": "^6.0.3",
16 | "clvm": "^1.0.9",
17 | "postcss": "^8.4.5",
18 | "react": "^17.0.2",
19 | "react-dom": "^17.0.2",
20 | "tailwindcss": "^3.0.15"
21 | },
22 | "devDependencies": {
23 | "@types/antd": "^1.0.0",
24 | "@types/autoprefixer": "^10.2.0",
25 | "@types/less": "^3.0.3",
26 | "@types/node": "^17.0.9",
27 | "@types/react": "^17.0.33",
28 | "@types/react-dom": "^17.0.10",
29 | "@types/tailwindcss": "^3.0.2",
30 | "@typescript-eslint/eslint-plugin": "^5.9.1",
31 | "@typescript-eslint/parser": "^5.9.1",
32 | "@vitejs/plugin-react": "^1.0.7",
33 | "eslint": "^8.7.0",
34 | "eslint-plugin-react": "^7.28.0",
35 | "less": "^4.1.2",
36 | "typescript": "^4.4.4",
37 | "vite": "^2.7.2"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/src/App.less:
--------------------------------------------------------------------------------
1 | @import '~antd/dist/antd.less';
2 |
3 | @primary-color: #3aac59;
4 | @border-radius-base: 20px;
5 | @shadow-color: @primary-color;
6 |
7 | .App {
8 | min-height: 100%;
9 | }
10 |
11 | .container {
12 | width: 1166px;
13 | margin: 0 auto;
14 | }
15 |
16 | .select-theme {
17 | border-radius: 10px;
18 |
19 | input,
20 | .ant-select-selector {
21 | border-radius: 10px !important;
22 | height: 48px !important;
23 | background-color: #f3f5f3 !important;
24 | border: 1px solid #f3f5f3 !important;
25 | }
26 |
27 | .ant-select-selection-item {
28 | line-height: 45px !important;
29 | font-weight: bold !important;
30 | }
31 | }
32 |
33 | .input-theme {
34 | height: 48px;
35 | border-radius: 10px;
36 | background-color: #f3f5f3;
37 | border: 1px solid #f3f5f3;
38 |
39 | .ant-input {
40 | background-color: #f3f5f3;
41 | border-radius: 0;
42 | }
43 | }
44 |
45 | .ant-form-item-label {
46 | font-weight: bold;
47 | }
48 |
49 | .btn-theme {
50 | height: 58px;
51 | line-height: 1;
52 | font-size: 16px;
53 | font-weight: bold;
54 | border-radius: 10px;
55 | opacity: 0.8;
56 |
57 | &.ant-btn-loading {
58 | background-color: #dedede;
59 | border-color: #dedede;
60 | color: #000;
61 | opacity: 1;
62 | }
63 | }
64 |
65 | .btn-theme-color {
66 | background: linear-gradient(
67 | 95.91deg,
68 | #63d17b 4.06%,
69 | #26ddc7 93.65%
70 | ) !important;
71 | border: none;
72 | border-radius: 30px;
73 | text-shadow: none;
74 |
75 | &:hover {
76 | opacity: 0.9;
77 | }
78 | }
79 |
80 | .modal-theme {
81 | .ant-modal-content {
82 | border-radius: 40px;
83 | }
84 | }
85 |
86 | .popover-theme {
87 | .ant-popover-arrow {
88 | display: none;
89 | }
90 |
91 | .ant-popover-inner-content {
92 | margin-top: -5px !important;
93 | }
94 | }
95 |
96 | .modal-theme2 {
97 | .ant-modal-header {
98 | border: none;
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from 'antd';
2 | import { useEffect, useState } from 'react';
3 | import './App.less';
4 | import bgSvg from './assets/images/bg.svg';
5 | import svg2 from './assets/images/img-10.svg';
6 | import svg3 from './assets/images/img-3.svg';
7 | import svg4 from './assets/images/img-4.svg';
8 | import svg5 from './assets/images/img-5.svg';
9 | import svg6 from './assets/images/img-6.svg';
10 | import svg1 from './assets/images/img-7.svg';
11 | import logo from './assets/images/full_logo.svg';
12 | import Donate from './components/Donate';
13 | import Login from './components/Login';
14 | import UserInfo from './components/UserInfo';
15 |
16 | const links = [
17 | {
18 | icon: svg1,
19 | url: 'https://twitter.com/goby_app'
20 | },
21 | {
22 | icon: svg2,
23 | url: 'https://discord.gg/rZFf5dugft'
24 | },
25 | {
26 | icon: svg3,
27 | url: 'https://goby-app.medium.com/'
28 | },
29 | {
30 | icon: svg5,
31 | url: 'https://github.com/GobyWallet'
32 | },
33 | {
34 | icon: svg6,
35 | url: 'mailto:dimitry@goby.app'
36 | },
37 | ]
38 |
39 | declare global {
40 | interface Window {
41 | chia: any;
42 | }
43 | }
44 |
45 | function App() {
46 | const [loginVisible, setLoginVisible] = useState(false)
47 | // const [user, setUser] = useState(false)
48 | const [account, setAccount] = useState(null);
49 |
50 | const handleLogin = () => {
51 | setLoginVisible(false)
52 | // setUser(true)
53 | }
54 | const isGobyInstalled = () => {
55 | const { chia } = window;
56 | return Boolean(chia && chia.isGoby)
57 | }
58 |
59 | const init = async () => {
60 | if (isGobyInstalled()) {
61 | window.chia.on('accountsChanged', (accounts: string[]) => {
62 | setAccount(accounts?.[0]);
63 | })
64 | window.chia.on('chainChanged', () => window.location.reload());
65 |
66 | window.chia.request({method: 'accounts'}).then((accounts: string[]) => {
67 | setAccount(accounts?.[0]);
68 | })
69 | }
70 | }
71 |
72 | const handleConnect = async () => {
73 | if (isGobyInstalled()) {
74 | const accounts = await window.chia.request({method: 'requestAccounts'});
75 | setAccount(accounts?.[0]);
76 | } else {
77 | setLoginVisible(true);
78 | }
79 | }
80 |
81 | useEffect(() => {
82 | init();
83 | }, []);
84 |
85 | return (
86 |
89 |
90 |
91 |
92 |
93 |
94 | {account ? : }
97 |
98 |
99 |
100 |
101 |
102 |
103 |
123 |
124 |
setLoginVisible(false)} />
125 |
126 | )
127 | }
128 |
129 |
130 | export default App
131 |
--------------------------------------------------------------------------------
/src/api.tsx:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | export async function getBalanceByAddress(chainAddress: string): Promise {
4 | const resp = await axios.get("https://api.goby.app/v1/balance", {params: {address: chainAddress}});
5 | return resp.data.amount;
6 | }
--------------------------------------------------------------------------------
/src/assets/images/bg.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/assets/images/full_logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/img-1.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/assets/images/img-10.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/img-2.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/src/assets/images/img-3.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/img-4.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/img-5.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/img-6.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/img-7.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/img-8.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/images/img-9.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/assets/images/logo.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/chia-util.tsx:
--------------------------------------------------------------------------------
1 | import { bech32m } from 'bech32';
2 | import { Buffer } from 'buffer';
3 | import * as clvm from 'clvm';
4 | import { SHA256 } from 'jscrypto/es6/SHA256';
5 | import { Hex } from 'jscrypto/es6/Hex';
6 | import BigNumber from 'bignumber.js';
7 |
8 | export function sexpTreeHash(pz: clvm.SExp, precalculated: string[] = []): Buffer {
9 | let buf;
10 | const p = pz.pair;
11 | if (p) {
12 | const left = sexpTreeHash(p[0], precalculated);
13 | const right = sexpTreeHash(p[1], precalculated);
14 | buf = Buffer.concat([Buffer.from([2]), left, right]);
15 | } else {
16 | if (precalculated?.find(item => item == Buffer.from(pz.atom!.raw()).toString('hex'))) {
17 | return Buffer.from((pz.atom as any).raw());
18 | }
19 | buf = Buffer.concat([Buffer.from([1]), (pz.atom as any).raw()]);
20 | }
21 | return Buffer.from(SHA256.hash(Hex.parse(buf.toString('hex'))).toString(Hex), 'hex');
22 | }
23 |
24 | export function toCatPuzzleHash(assetId: string, innerPuzzleHash: string) {
25 | const pz_hex = (
26 | 'ff02ffff01ff02ffff01ff02ff5effff04ff02ffff04ffff04ff05ffff04ffff0bff2cff0580ffff04ff0bff80808080ffff04ffff02ff17ff2f80ffff04ff5fffff04ffff02ff2effff04ff02ffff04ff17ff80808080ffff04ffff0bff82027fff82057fff820b7f80ffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffffff04ff820bffff80808080808080808080808080ffff04ffff01ffffffff81ca3dff46ff0233ffff3c04ff01ff0181cbffffff02ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff22ffff0bff2cff3480ffff0bff22ffff0bff22ffff0bff2cff5c80ff0980ffff0bff22ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff0bffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff13ff80808080ff820b9f80ffff01ff02ff26ffff04ff02ffff04ffff02ff13ffff04ff5fffff04ff17ffff04ff2fffff04ff81bfffff04ff82017fffff04ff1bff8080808080808080ffff04ff82017fff8080808080ffff01ff088080ff0180ffff01ff02ffff03ff17ffff01ff02ffff03ffff20ff81bf80ffff0182017fffff01ff088080ff0180ffff01ff088080ff018080ff0180ffff04ffff04ff05ff2780ffff04ffff10ff0bff5780ff778080ff02ffff03ff05ffff01ff02ffff03ffff09ffff02ffff03ffff09ff11ff7880ffff0159ff8080ff0180ffff01818f80ffff01ff02ff7affff04ff02ffff04ff0dffff04ff0bffff04ffff04ff81b9ff82017980ff808080808080ffff01ff02ff5affff04ff02ffff04ffff02ffff03ffff09ff11ff7880ffff01ff04ff78ffff04ffff02ff36ffff04ff02ffff04ff13ffff04ff29ffff04ffff0bff2cff5b80ffff04ff2bff80808080808080ff398080ffff01ff02ffff03ffff09ff11ff2480ffff01ff04ff24ffff04ffff0bff20ff2980ff398080ffff010980ff018080ff0180ffff04ffff02ffff03ffff09ff11ff7880ffff0159ff8080ff0180ffff04ffff02ff7affff04ff02ffff04ff0dffff04ff0bffff04ff17ff808080808080ff80808080808080ff0180ffff01ff04ff80ffff04ff80ff17808080ff0180ffffff02ffff03ff05ffff01ff04ff09ffff02ff26ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff0bff22ffff0bff2cff5880ffff0bff22ffff0bff22ffff0bff2cff5c80ff0580ffff0bff22ffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bff2cff058080ff0180ffff04ffff04ff28ffff04ff5fff808080ffff02ff7effff04ff02ffff04ffff04ffff04ff2fff0580ffff04ff5fff82017f8080ffff04ffff02ff7affff04ff02ffff04ff0bffff04ff05ffff01ff808080808080ffff04ff17ffff04ff81bfffff04ff82017fffff04ffff0bff8204ffffff02ff36ffff04ff02ffff04ff09ffff04ff820affffff04ffff0bff2cff2d80ffff04ff15ff80808080808080ff8216ff80ffff04ff8205ffffff04ff820bffff808080808080808080808080ff02ff2affff04ff02ffff04ff5fffff04ff3bffff04ffff02ffff03ff17ffff01ff09ff2dffff0bff27ffff02ff36ffff04ff02ffff04ff29ffff04ff57ffff04ffff0bff2cff81b980ffff04ff59ff80808080808080ff81b78080ff8080ff0180ffff04ff17ffff04ff05ffff04ff8202ffffff04ffff04ffff04ff24ffff04ffff0bff7cff2fff82017f80ff808080ffff04ffff04ff30ffff04ffff0bff81bfffff0bff7cff15ffff10ff82017fffff11ff8202dfff2b80ff8202ff808080ff808080ff138080ff80808080808080808080ff018080ffff04ffff01a072dec062874cd4d3aab892a0906688a1ae412b0109982e1797a170add88bdcdcffff04ffff01a0' +
27 | assetId +
28 | 'ffff04ffff01a0' +
29 | innerPuzzleHash +
30 | 'ff0180808080'
31 | );
32 | const b = clvm.Bytes.from(pz_hex, 'hex');
33 | const s = new clvm.Stream(b);
34 | const pz = clvm.sexp_from_stream(new clvm.Stream(b), clvm.SExp.to);
35 | return sexpTreeHash(pz, [innerPuzzleHash]);
36 | }
37 |
38 | export function toChainAddress(address: string | Buffer) {
39 | if (!Buffer.isBuffer(address)) {
40 | address = Buffer.from(address, 'hex');
41 | }
42 | return bech32m.encode('xch', bech32m.toWords(address));
43 | }
44 |
45 | export const isValidAddress = (address: string) => {
46 | if (!address) return false;
47 | try {
48 | const data = bech32m.decode(address);
49 | return data && data.words && data.words.length == 52;
50 | } catch (error) {
51 | return false;
52 | }
53 | }
54 |
55 | export function shortenAddress(address = '') {
56 | if (address.length < 11) {
57 | return address;
58 | }
59 |
60 | return `${address.slice(0, 5)}...${address.slice(-4,)}`;
61 | }
62 |
63 | export const splitNumberByStep = (
64 | num: number | string,
65 | step = 3,
66 | symbol = ',',
67 | forceInt = false
68 | ) => {
69 | // eslint-disable-next-line prefer-const
70 | let [int, float] = (num + '').split('.');
71 | const reg = new RegExp(`(\\d)(?=(\\d{${step}})+(?!\\d))`, 'g');
72 |
73 | int = int.replace(reg, `$1${symbol}`);
74 | if (Number(num) > 1000000 || forceInt) {
75 | // hide the after-point part if number is more than 1000000
76 | float = '';
77 | }
78 | if (float) {
79 | return `${int}.${float}`;
80 | }
81 | return int;
82 | };
83 |
84 | export function toDecimalAmount(amount: number | BigNumber | string, decimals = 12) {
85 | if (!amount) return '0';
86 | const bn = new BigNumber(amount).div(Math.pow(10, decimals));
87 | const str = bn.toFixed();
88 | const split = str.split('.');
89 | if (!split[1] || split[1].length < decimals) {
90 | return splitNumberByStep(bn.toFixed());
91 | }
92 | return splitNumberByStep(bn.toFixed(decimals));
93 | }
--------------------------------------------------------------------------------
/src/components/Donate.less:
--------------------------------------------------------------------------------
1 | .donate-container {
2 | .donate-success {
3 | transition: 0.3s;
4 | transform: scale(0);
5 | opacity: 0;
6 | }
7 |
8 | &.done {
9 | .donate-success {
10 | transform: scale(1);
11 | opacity: 1;
12 | }
13 | .avatar-container {
14 | top: 0 !important;
15 | }
16 | }
17 | }
18 |
19 | .avatar-container {
20 | transition: 0.4s;
21 | &:hover {
22 | > div:nth-child(2) {
23 | transform: translate(55px, -30px) rotate(55deg);
24 | }
25 | }
26 |
27 | > div {
28 | cursor: pointer;
29 | transition: 0.3s;
30 |
31 | &:nth-child(1) {
32 | z-index: 1;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/Donate.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Col, Form, Input, notification, Row } from 'antd'
2 | import BigNumber from 'bignumber.js'
3 | import React, { useEffect, useState } from 'react'
4 | import { getBalanceByAddress } from '../api'
5 | import avatarSvg from '../assets/images/img-1.svg'
6 | import avatar2Svg from '../assets/images/img-2.svg'
7 | import { isValidAddress, toCatPuzzleHash, toChainAddress, toDecimalAmount } from '../chia-util'
8 | import './Donate.less'
9 | import TokenSelect from './TokenSelect'
10 |
11 | const ASSET_LIST = [
12 | {
13 | symbol: 'XCH',
14 | assetId: '',
15 | decimals: 12,
16 | logo: 'https://static.goby.app/image/token/xch/XCH_32.png',
17 | },
18 | {
19 | symbol: 'USDS',
20 | assetId: '6d95dae356e32a71db5ddcb42224754a02524c615c5fc35f568c2af04774e589',
21 | decimals: 3,
22 | logo: 'https://static.goby.app/image/token/usds/USDS_32.png'
23 | }
24 | ];
25 |
26 | type DonateProps = {
27 | account: string | null
28 | }
29 |
30 |
31 | const Donate: React.FC = ({ account }) => {
32 | const [targets, setTargets] = useState([
33 | {
34 | value: 'xch1r54e052uwuadu2v8cuakayr2ktkr5r7z8t9nnmced08ng6flq6mqrlxgke',
35 | icon: avatarSvg
36 | },
37 | {
38 | value: 'xch1kj5gvmtl8xc874lkxdnvf9js4c262pk5jdnt42mqcv4nkqqt3m5s2gee6z',
39 | icon: avatar2Svg
40 | }
41 | ])
42 | const [loading, setLoading] = useState(false)
43 | const [done, setDone] = useState(false)
44 | const [currentAsset, setCurrentAsset] = useState(ASSET_LIST[0]);
45 | const [tokenBalance, setTokenBalance] = useState(null);
46 | const handleSwitchTarget = () => {
47 | const nextTarget = targets.sort(() => -1)
48 | setTargets([...nextTarget]);
49 | const values = form.getFieldsValue();
50 | form.setFieldsValue({
51 | ...values,
52 | to: nextTarget[0].value
53 | })
54 | }
55 | const { useForm } = Form;
56 | const [form] = useForm<{ to: string; amount: string }>();
57 | const canSubmit = () => {
58 | return account && isValidAddress(form.getFieldValue('to')) &&
59 | new BigNumber(form.getFieldValue('amount')).isGreaterThan(0);
60 | }
61 |
62 | const handleFormValuesChange = async (_: any, { to, amount }: { to: string; amount: string }) => {
63 | let resultAmount = amount;
64 | if (!/^\d*(\.\d*)?$/.test(amount)) {
65 | resultAmount = "0";
66 | }
67 | form.setFieldsValue({
68 | to,
69 | amount: resultAmount,
70 | })
71 | }
72 | useEffect(() => {
73 | setTokenBalance(null);
74 | if (!account) {
75 | return;
76 | }
77 | (async () => {
78 | let puzzleHash = account!;
79 | if (currentAsset.assetId) {
80 | puzzleHash = toCatPuzzleHash(currentAsset.assetId, puzzleHash).toString('hex');
81 | }
82 | const amount = await getBalanceByAddress(toChainAddress(puzzleHash));
83 | setTokenBalance(amount);
84 | })();
85 | }, [currentAsset, account]);
86 |
87 | const handleAssetSelect = (asset: any) => {
88 | setCurrentAsset(asset);
89 | const values = form.getFieldsValue();
90 | form.setFieldsValue({
91 | ...values,
92 | amount: ''
93 | })
94 | }
95 |
96 | const handleSubmit = async ({ to, amount }: { to: string, amount: string }) => {
97 | setLoading(true)
98 | const sendAmount = new BigNumber(amount)
99 | .multipliedBy(10 ** currentAsset.decimals)
100 | .toFixed(0);
101 | const params = {
102 | to,
103 | amount: sendAmount,
104 | assetId: currentAsset.assetId,
105 | }
106 | try {
107 | await window.chia.request({ method: 'transfer', params })
108 | setDone(true)
109 | } catch (error: any) {
110 | console.log(error);
111 | notification.error({
112 | style: {
113 | width: '340px'
114 | },
115 | duration: 3,
116 | message: 'Error',
117 | description: error.message,
118 | });
119 | }
120 | setLoading(false)
121 | }
122 |
123 | return (
124 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 | {currentAsset.symbol}} />
173 |
174 |
175 |
176 | Balance:{tokenBalance == null ? "-": toDecimalAmount(tokenBalance, currentAsset.decimals)}
177 |
178 |
179 |
180 |
183 |
184 |
185 |
186 |
196 |
197 |
Sent Successfully
198 |
setDone(false)}>
199 |
200 | Back
201 |
202 |
203 |
204 |
205 | )
206 | }
207 |
208 | export default Donate
209 |
--------------------------------------------------------------------------------
/src/components/Login.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Modal, ModalProps } from "antd";
2 | import React from "react";
3 | import "./Donate.less";
4 |
5 | interface Props extends ModalProps {
6 | onSuccess: () => void;
7 | }
8 |
9 | const Login: React.FC = (props) => {
10 | return (
11 |
18 |
19 |
47 |
48 |
You haven’t installed any browser wallet
49 |
64 |
65 |
66 | );
67 | };
68 |
69 | export default Login;
70 |
--------------------------------------------------------------------------------
/src/components/TokenSelect.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { Input, Modal } from 'antd'
3 | import React, { useState } from 'react'
4 | import './Donate.less'
5 |
6 |
7 | type TokenSelectProps = {
8 | selectedAsset: any,
9 | tokenList: any[],
10 | onAssetSelected: any,
11 | }
12 |
13 | const TokenSelect: React.FC = ({selectedAsset, tokenList, onAssetSelected}) => {
14 | const [visible, setVisible] = useState(false)
15 | const handleAssetSelect = (asset: any) => {
16 | setVisible(false);
17 | onAssetSelected && onAssetSelected(asset);
18 | }
19 | return (
20 | <>
21 | setVisible(true)} className="bg-[#f3f5f3] cursor-pointer justify-between text-[14px] font-bold items-center flex px-[10px] w-full h-[48px] rounded-[10px]">
22 |
23 |
24 | {selectedAsset.symbol}
25 |
26 |
27 |
30 |
31 |
32 |
33 | setVisible(false)} visible={visible} className="modal-theme2" width="386px" title="CAT List" footer={false}>
34 | {/*
35 |
36 |
*/}
37 |
38 |
39 | {tokenList.map(item =>
40 | - handleAssetSelect(item)}
42 | className="cursor-pointer hover:opacity-80 flex mb-[20px] items-center justify-between">
43 |
44 |
45 | {item.symbol}
46 |
47 |
48 | )}
49 |
50 |
51 | >
52 | )
53 | }
54 |
55 | export default TokenSelect
56 |
--------------------------------------------------------------------------------
/src/components/UserInfo.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Popover } from 'antd'
2 | import { useEffect, useState } from 'react';
3 | import { getBalanceByAddress } from '../api';
4 | import { toChainAddress, toDecimalAmount } from '../chia-util';
5 |
6 | const UserInfo = ({account}: {account: string}) => {
7 | const chainAddress = toChainAddress(account);
8 | const [balance, setBalance] = useState(null);
9 |
10 | useEffect(() => {
11 | (async () => {
12 | const amount = await getBalanceByAddress(chainAddress);
13 | setBalance(amount);
14 | })();
15 | }, [chainAddress]);
16 | const content =
17 | {chainAddress}
18 |
19 |
20 | return (
21 |
22 |
23 |
{balance == null? "-" : toDecimalAmount(balance)} XCH
24 |
25 | {`${chainAddress.slice(0, 6)}...${chainAddress.slice(-4)}`}
26 |
31 |
32 |
33 |
34 | )
35 | }
36 |
37 | export default UserInfo
38 |
--------------------------------------------------------------------------------
/src/favicon.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | html,
6 | body {
7 | height: 100%;
8 | padding: 0;
9 | margin: 0;
10 | font-family: 'Poppins', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
11 | Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
12 |
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | }
16 |
17 | a {
18 | color: inherit;
19 | text-decoration: none;
20 | }
21 |
22 | a:hover {
23 | opacity: 0.8;
24 | }
25 |
26 | a:active {
27 | opacity: 0.9;
28 | }
29 |
30 | * {
31 | box-sizing: border-box;
32 | }
33 |
34 | #root {
35 | min-height: 100%;
36 | }
37 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom'
2 | import './index.css'
3 | import App from './App'
4 |
5 | ReactDOM.render(
6 | ,
7 | document.getElementById('root')
8 | )
9 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | important: true,
3 | content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [],
8 | }
9 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": false,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["./src"]
20 | }
21 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | resolve: {
8 | alias: [{ find: /^~/, replacement: '' }],
9 | },
10 | css: {
11 | preprocessorOptions: {
12 | less: {
13 | javascriptEnabled: true,
14 | },
15 | },
16 | },
17 | server: {
18 | proxy: {
19 | "/v1": {
20 | target: 'https://api.goby.app/',
21 | changeOrigin: true,
22 | secure: false,
23 | }
24 | }
25 | }
26 | })
27 |
--------------------------------------------------------------------------------