├── docs
├── privacy-policy.txt
├── terms-of-use.txt
├── tc-verify.json
├── apple-touch-icon.png
├── tonconnect-manifest.json
├── tonconnect-manifest.vercel.json
├── index.html
└── assets
│ └── index-f6cd27ed.css
├── public
├── terms-of-use.txt
├── privacy-policy.txt
├── tc-verify.json
├── apple-touch-icon.png
├── tonconnect-manifest.json
└── tonconnect-manifest.vercel.json
├── src
├── vite-env.d.ts
├── App.scss
├── components
│ ├── Footer
│ │ ├── ColorsSelect
│ │ │ ├── style.scss
│ │ │ └── ColorsSelect.tsx
│ │ ├── ColorsModal
│ │ │ ├── style.scss
│ │ │ └── ColorsModal.tsx
│ │ ├── footer.scss
│ │ └── Footer.tsx
│ ├── Header
│ │ ├── Header.tsx
│ │ └── header.scss
│ ├── TxForm
│ │ ├── style.scss
│ │ └── TxForm.tsx
│ └── TonProofDemo
│ │ ├── style.scss
│ │ └── TonProofDemo.tsx
├── main.tsx
├── index.scss
├── hooks
│ └── useInterval.ts
├── App.tsx
├── patch-local-storage-for-github-pages.ts
├── trackers.ts
├── TonProofDemoApi.ts
└── assets
│ └── react.svg
├── tsconfig.node.json
├── .gitignore
├── vite.config.ts
├── tsconfig.json
├── index.html
├── package.json
└── README.md
/docs/privacy-policy.txt:
--------------------------------------------------------------------------------
1 | Privacy Policy example
2 | ...
3 |
--------------------------------------------------------------------------------
/docs/terms-of-use.txt:
--------------------------------------------------------------------------------
1 | Terms of use example
2 | ...
3 |
--------------------------------------------------------------------------------
/public/terms-of-use.txt:
--------------------------------------------------------------------------------
1 | Terms of use example
2 | ...
3 |
--------------------------------------------------------------------------------
/public/privacy-policy.txt:
--------------------------------------------------------------------------------
1 | Privacy Policy example
2 | ...
3 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/docs/tc-verify.json:
--------------------------------------------------------------------------------
1 | {
2 | "payload": "9hE-Ov4dBR8AAAAAZT_L0NIswgedrN4hc4upm0e9vASVO4nl_MP5hbybtMYIfhkZ"
3 | }
4 |
--------------------------------------------------------------------------------
/docs/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ton-connect/demo-dapp-with-wallet/master/docs/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/tc-verify.json:
--------------------------------------------------------------------------------
1 | {
2 | "payload": "9hE-Ov4dBR8AAAAAZT_L0NIswgedrN4hc4upm0e9vASVO4nl_MP5hbybtMYIfhkZ"
3 | }
4 |
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ton-connect/demo-dapp-with-wallet/master/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/src/App.scss:
--------------------------------------------------------------------------------
1 | .app {
2 | height: 100%;
3 | display: flex;
4 | flex-direction: column;
5 |
6 | > header {
7 | margin-bottom: 10px;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/Footer/ColorsSelect/style.scss:
--------------------------------------------------------------------------------
1 | .colors-container {
2 | > div {
3 | margin-bottom: 20px;
4 |
5 | > span {
6 | margin-right: 14px;
7 | font-weight: bold;
8 | }
9 |
10 | > label {
11 | margin-right: 10px;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/Header/Header.tsx:
--------------------------------------------------------------------------------
1 | import {TonConnectButton} from "@tonconnect/ui-react";
2 | import './header.scss';
3 |
4 | export const Header = () => {
5 | return
6 | My App with React UI
7 |
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 | .env
15 |
16 | # Editor directories and files
17 | .vscode/*
18 | !.vscode/extensions.json
19 | .idea
20 | .DS_Store
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw?
26 | .ssh
27 |
--------------------------------------------------------------------------------
/docs/tonconnect-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "url": "https://tonconnect-demo-dapp-with-wallet.vercel.app/",
3 | "name": "Demo Dapp with wallet",
4 | "iconUrl": "https://ton-connect.github.io/demo-dapp-with-wallet/apple-touch-icon.png",
5 | "termsOfUseUrl": "https://ton-connect.github.io/demo-dapp-with-wallet/terms-of-use.txt",
6 | "privacyPolicyUrl": "https://ton-connect.github.io/demo-dapp-with-wallet/privacy-policy.txt"
7 | }
8 |
--------------------------------------------------------------------------------
/public/tonconnect-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "url": "https://tonconnect-demo-dapp-with-wallet.vercel.app/",
3 | "name": "Demo Dapp with wallet",
4 | "iconUrl": "https://ton-connect.github.io/demo-dapp-with-wallet/apple-touch-icon.png",
5 | "termsOfUseUrl": "https://ton-connect.github.io/demo-dapp-with-wallet/terms-of-use.txt",
6 | "privacyPolicyUrl": "https://ton-connect.github.io/demo-dapp-with-wallet/privacy-policy.txt"
7 | }
8 |
--------------------------------------------------------------------------------
/docs/tonconnect-manifest.vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "url": "https://tonconnect-demo-dapp-with-wallet.vercel.app/",
3 | "name": "Demo Dapp with React UI",
4 | "iconUrl": "https://tonconnect-demo-dapp-with-wallet.vercel.app/apple-touch-icon.png",
5 | "termsOfUseUrl": "https://tonconnect-demo-dapp-with-wallet.vercel.app/terms-of-use.txt",
6 | "privacyPolicyUrl": "https://tonconnect-demo-dapp-with-wallet.vercel.app/privacy-policy.txt"
7 | }
8 |
--------------------------------------------------------------------------------
/public/tonconnect-manifest.vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "url": "https://tonconnect-demo-dapp-with-wallet.vercel.app/",
3 | "name": "Demo Dapp with React UI",
4 | "iconUrl": "https://tonconnect-demo-dapp-with-wallet.vercel.app/apple-touch-icon.png",
5 | "termsOfUseUrl": "https://tonconnect-demo-dapp-with-wallet.vercel.app/terms-of-use.txt",
6 | "privacyPolicyUrl": "https://tonconnect-demo-dapp-with-wallet.vercel.app/privacy-policy.txt"
7 | }
8 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 |
6 | export default defineConfig({
7 | plugins: [react()],
8 | build: {
9 | outDir: 'docs'
10 | },
11 | // @ts-ignore
12 | base: process.env.GH_PAGES ? '/demo-dapp-with-wallet/' : './',
13 | server: {
14 | fs: {
15 | allow: ['../sdk', './'],
16 | },
17 | },
18 | })
19 |
--------------------------------------------------------------------------------
/src/components/Footer/ColorsModal/style.scss:
--------------------------------------------------------------------------------
1 | .modal {
2 | position: fixed;
3 | left: 0;
4 | top: 0;
5 | bottom: 0;
6 | right: 0;
7 | z-index: 10000000;
8 |
9 | background-color: rgb(16, 22, 31);
10 |
11 | padding: 20px;
12 |
13 | color: white;
14 |
15 | > button {
16 | float: right;
17 | }
18 |
19 | &__toggle {
20 | display: flex;
21 | justify-content: center;
22 | gap: 20px;
23 |
24 | > a {
25 | color: white;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/Header/header.scss:
--------------------------------------------------------------------------------
1 | header {
2 | display: flex;
3 | align-items: center;
4 | justify-content: space-between;
5 | padding: 10px 25px;
6 |
7 | > span {
8 | font-size: 30px;
9 | line-height: 34px;
10 | color: rgba(102,170,238,0.91);
11 | font-weight: bold;
12 | }
13 | }
14 |
15 | @media (max-width: 525px) {
16 | header {
17 | flex-direction: column;
18 | gap: 10px;
19 |
20 | > *:nth-child(2) {
21 | align-self: flex-end;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/Footer/footer.scss:
--------------------------------------------------------------------------------
1 | .footer {
2 | padding: 20px;
3 | display: flex;
4 | gap: 20px;
5 | justify-content: flex-end;
6 | align-items: center;
7 | flex-wrap: wrap;
8 |
9 | > div > label {
10 | color: white;
11 | margin-right: 5px;
12 | }
13 | }
14 |
15 | .footer-checkbox-container {
16 | display: flex;
17 | flex-direction: column;
18 |
19 | > span {
20 | color: white;
21 | font-weight: bold;
22 | margin-bottom: 4px;
23 | }
24 |
25 | input {
26 | margin-left: 3px;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import './patch-local-storage-for-github-pages';
2 |
3 | import React, {StrictMode} from 'react'
4 | import { render } from 'react-dom';
5 | import App from './App'
6 | import './index.scss'
7 | import eruda from "eruda";
8 | import { enableQaMode } from '@tonconnect/ui-react';
9 |
10 | if (import.meta.env.VITE_QA_MODE === 'enable') {
11 | enableQaMode();
12 | }
13 |
14 | eruda.init();
15 |
16 | render(
17 |
18 |
19 | ,
20 | document.getElementById('root') as HTMLElement
21 | )
22 |
--------------------------------------------------------------------------------
/src/index.scss:
--------------------------------------------------------------------------------
1 | html, body, #root {
2 | height: 100%;
3 | }
4 |
5 | body {
6 | margin: 0;
7 | background-color: rgba(16, 22, 31, 0.92);;
8 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
9 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
10 | sans-serif;
11 | -webkit-font-smoothing: antialiased;
12 | -moz-osx-font-smoothing: grayscale;
13 | }
14 |
15 | code {
16 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
17 | monospace;
18 | }
19 |
20 | * {
21 | box-sizing: border-box;
22 | }
23 |
--------------------------------------------------------------------------------
/src/hooks/useInterval.ts:
--------------------------------------------------------------------------------
1 | import {useEffect, useLayoutEffect, useRef} from 'react'
2 |
3 | function useInterval(callback: () => void, delay: number | null) {
4 | const savedCallback = useRef(callback)
5 |
6 | useLayoutEffect(() => {
7 | savedCallback.current = callback
8 | }, [callback])
9 |
10 | useEffect(() => {
11 | if (!delay && delay !== 0) {
12 | return
13 | }
14 |
15 | const id = setInterval(() => savedCallback.current(), delay)
16 |
17 | return () => clearInterval(id)
18 | }, [delay])
19 | }
20 |
21 | export default useInterval
22 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"],
20 | "references": [{ "path": "./tsconfig.node.json" }]
21 | }
22 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Demo Dapp with @tonconnect/ui-react
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import './App.scss';
2 | import './trackers';
3 | import {THEME, TonConnectUIProvider} from "@tonconnect/ui-react";
4 | import {Footer} from "./components/Footer/Footer";
5 | import {Header} from "./components/Header/Header";
6 | import {TxForm} from "./components/TxForm/TxForm";
7 |
8 | function App() {
9 | return (
10 |
17 |
18 |
19 |
20 | {/**/}
21 |
22 |
23 |
24 | )
25 | }
26 |
27 | export default App
28 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Demo Dapp with @tonconnect/ui-react
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/components/TxForm/style.scss:
--------------------------------------------------------------------------------
1 | .send-tx-form {
2 | flex: 1;
3 | display: flex;
4 | width: 100%;
5 | flex-direction: column;
6 | gap: 20px;
7 | padding: 20px;
8 | align-items: center;
9 |
10 | h3 {
11 | color: white;
12 | opacity: 0.8;
13 | font-size: 28px;
14 | }
15 |
16 | > div:nth-child(2) {
17 | width: 100%;
18 |
19 | span {
20 | word-break: break-word;
21 | }
22 | }
23 |
24 | > button {
25 | border: none;
26 | padding: 7px 15px;
27 | border-radius: 15px;
28 | cursor: pointer;
29 |
30 | background-color: rgba(102,170,238,0.91);
31 | color: white;
32 | font-size: 16px;
33 | line-height: 20px;
34 |
35 | transition: transform 0.1s ease-in-out;
36 |
37 | &:hover {
38 | transform: scale(1.03);
39 | }
40 |
41 | &:active {
42 | transform: scale(0.97);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/patch-local-storage-for-github-pages.ts:
--------------------------------------------------------------------------------
1 | const separator = window.location.pathname.replace(/\/+$/, '') + ':';
2 |
3 | const setItem = localStorage.setItem;
4 | localStorage.constructor.prototype.setItem = (key: unknown, value: string) => setItem.apply(localStorage, [separator + key, value]);
5 | localStorage.setItem = (key: unknown, value: string) => setItem.apply(localStorage, [separator + key, value]);
6 |
7 | const getItem = localStorage.getItem;
8 | localStorage.constructor.prototype.getItem = (key: unknown) => getItem.apply(localStorage, [separator + key]);
9 | localStorage.getItem = (key: unknown) => getItem.apply(localStorage, [separator + key]);
10 |
11 | const removeItem = localStorage.removeItem;
12 | localStorage.constructor.prototype.removeItem = (key: unknown) => removeItem.apply(localStorage, [separator + key]);
13 | localStorage.removeItem = (key: unknown) => removeItem.apply(localStorage, [separator + key]);
14 |
15 | export {};
16 |
--------------------------------------------------------------------------------
/src/components/TonProofDemo/style.scss:
--------------------------------------------------------------------------------
1 | .ton-proof-demo {
2 | display: flex;
3 | width: 100%;
4 | flex-direction: column;
5 | gap: 20px;
6 | align-items: center;
7 | margin-top: 60px;
8 | padding: 20px;
9 |
10 | h3 {
11 | color: white;
12 | opacity: 0.8;
13 | }
14 |
15 | > div:nth-child(3) {
16 | width: 100%;
17 |
18 | span {
19 | word-break: break-word;
20 | }
21 | }
22 |
23 | &__error {
24 | color: rgba(102,170,238,0.91);
25 | font-size: 18px;
26 | line-height: 20px;
27 | }
28 |
29 | button {
30 | border: none;
31 | padding: 7px 15px;
32 | border-radius: 15px;
33 | cursor: pointer;
34 |
35 | background-color: rgba(102,170,238,0.91);
36 | color: white;
37 | font-size: 16px;
38 | line-height: 20px;
39 |
40 | transition: transform 0.1s ease-in-out;
41 |
42 | &:hover {
43 | transform: scale(1.03);
44 | }
45 |
46 | &:active {
47 | transform: scale(0.97);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/components/Footer/ColorsModal/ColorsModal.tsx:
--------------------------------------------------------------------------------
1 | import { THEME } from "@tonconnect/ui-react";
2 | import {useState} from "react";
3 | import {ColorsSelect} from "../ColorsSelect/ColorsSelect";
4 | import './style.scss';
5 |
6 | export const ColorsModal = () => {
7 | const [opened, setOpened] = useState(false);
8 | const [theme, setTheme] = useState(THEME.LIGHT);
9 |
10 | return(<>
11 |
12 | {opened &&
13 |
14 |
15 |
19 |
20 |
21 |
22 | }
23 | >
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo-dapp-react-ui",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "VITE_MANIFEST_URL=https://ton-connect.github.io/demo-dapp-with-wallet/tonconnect-manifest.json vite --host --force",
8 | "build": "tsc && GH_PAGES=true VITE_MANIFEST_URL=https://ton-connect.github.io/demo-dapp-with-wallet/tonconnect-manifest.json vite build",
9 | "build:dev": "tsc && VITE_MANIFEST_URL=https://tonconnect-demo-dapp-with-wallet.vercel.app/tonconnect-manifest.vercel.json vite build",
10 | "preview": "VITE_MANIFEST_URL=https://ton-connect.github.io/demo-dapp-with-react-ui/tonconnect-manifest.json vite preview"
11 | },
12 | "dependencies": {
13 | "@tonconnect/ui-react": "^2.3.0-beta.4",
14 | "eruda": "^2.11.2",
15 | "react": "^17.0.0",
16 | "react-dom": "^17.0.0",
17 | "react-json-view": "^1.21.3"
18 | },
19 | "devDependencies": {
20 | "@types/react": "^18.0.26",
21 | "@types/react-dom": "^18.0.9",
22 | "@vitejs/plugin-react": "^3.0.0",
23 | "sass": "^1.57.1",
24 | "typescript": "^4.9.3",
25 | "vite": "^4.0.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/trackers.ts:
--------------------------------------------------------------------------------
1 | import {UserActionEvent, SdkActionEvent} from "@tonconnect/ui-react";
2 |
3 | const logEvent = (scope: string): (event: Event) => void => {
4 | scope = scope.startsWith('ton-connect-ui-') ? 'TonConnectUI' : 'TonConnect';
5 |
6 | return (event: Event): void => {
7 | if (!(event instanceof CustomEvent)) {
8 | return;
9 | }
10 | const detail: UserActionEvent | SdkActionEvent = event.detail;
11 | console.log(`${scope} Event: ${detail.type}`, detail);
12 | }
13 | };
14 |
15 | const tonConnectUiPrefix = 'ton-connect-ui-';
16 | const tonConnectUiEvents = [
17 | 'request-version',
18 | 'response-version',
19 | 'connection-started',
20 | 'connection-completed',
21 | 'connection-error',
22 | 'connection-restoring-started',
23 | 'connection-restoring-completed',
24 | 'connection-restoring-error',
25 | 'transaction-sent-for-signature',
26 | 'transaction-signed',
27 | 'transaction-signing-failed',
28 | 'disconnection',
29 | ].map(event => `${tonConnectUiPrefix}${event}`)
30 |
31 | const tonConnectPrefix = 'ton-connect-';
32 | const tonConnectEvents = [
33 | 'request-version',
34 | 'response-version',
35 | 'connection-started',
36 | 'connection-completed',
37 | 'connection-error',
38 | 'connection-restoring-started',
39 | 'connection-restoring-completed',
40 | 'connection-restoring-error',
41 | 'transaction-sent-for-signature',
42 | 'transaction-signed',
43 | 'transaction-signing-failed',
44 | 'disconnection',
45 | ].map(event => `${tonConnectPrefix}${event}`)
46 |
47 | const events = [
48 | ...tonConnectUiEvents,
49 | ...tonConnectEvents
50 | ];
51 |
52 | for (const event of events) {
53 | try {
54 | window.addEventListener(`${event}`, logEvent(event));
55 | } catch (e) {}
56 | }
57 |
--------------------------------------------------------------------------------
/docs/assets/index-f6cd27ed.css:
--------------------------------------------------------------------------------
1 | .app{height:100%;display:flex;flex-direction:column}.app>header{margin-bottom:10px}.footer{padding:20px;display:flex;gap:20px;justify-content:flex-end;align-items:center;flex-wrap:wrap}.footer>div>label{color:#fff;margin-right:5px}.footer-checkbox-container{display:flex;flex-direction:column}.footer-checkbox-container>span{color:#fff;font-weight:700;margin-bottom:4px}.footer-checkbox-container input{margin-left:3px}.colors-container>div{margin-bottom:20px}.colors-container>div>span{margin-right:14px;font-weight:700}.colors-container>div>label{margin-right:10px}.modal{position:fixed;left:0;top:0;bottom:0;right:0;z-index:10000000;background-color:#10161f;padding:20px;color:#fff}.modal>button{float:right}.modal__toggle{display:flex;justify-content:center;gap:20px}.modal__toggle>a{color:#fff}header{display:flex;align-items:center;justify-content:space-between;padding:10px 25px}header>span{font-size:30px;line-height:34px;color:#66aaeee8;font-weight:700}@media (max-width: 525px){header{flex-direction:column;gap:10px}header>*:nth-child(2){align-self:flex-end}}.send-tx-form{flex:1;display:flex;width:100%;flex-direction:column;gap:20px;padding:20px;align-items:center}.send-tx-form h3{color:#fff;opacity:.8;font-size:28px}.send-tx-form>div:nth-child(2){width:100%}.send-tx-form>div:nth-child(2) span{word-break:break-word}.send-tx-form>button{border:none;padding:7px 15px;border-radius:15px;cursor:pointer;background-color:#66aaeee8;color:#fff;font-size:16px;line-height:20px;transition:transform .1s ease-in-out}.send-tx-form>button:hover{transform:scale(1.03)}.send-tx-form>button:active{transform:scale(.97)}html,body,#root{height:100%}body{margin:0;background-color:#10161feb;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}*{box-sizing:border-box}
2 |
--------------------------------------------------------------------------------
/src/TonProofDemoApi.ts:
--------------------------------------------------------------------------------
1 | import {Account, ConnectAdditionalRequest, TonProofItemReplySuccess} from "@tonconnect/ui-react";
2 | import './patch-local-storage-for-github-pages';
3 |
4 | class TonProofDemoApiService {
5 | private localStorageKey = 'demo-api-access-token';
6 |
7 | private host = 'https://demo.tonconnect.dev';
8 |
9 | public accessToken: string | null = null;
10 |
11 | public readonly refreshIntervalMs = 9 * 60 * 1000;
12 |
13 | constructor() {
14 | this.accessToken = localStorage.getItem(this.localStorageKey);
15 |
16 | if (!this.accessToken) {
17 | this.generatePayload();
18 | }
19 | }
20 |
21 | async generatePayload(): Promise {
22 | try {
23 | const response = await (
24 | await fetch(`${this.host}/ton-proof/generatePayload`, {
25 | method: 'POST',
26 | })
27 | ).json();
28 | return {tonProof: response.payload as string};
29 | } catch {
30 | return null;
31 | }
32 |
33 | }
34 |
35 | async checkProof(proof: TonProofItemReplySuccess['proof'], account: Account) {
36 | try {
37 | const reqBody = {
38 | address: account.address,
39 | network: account.chain,
40 | proof: {
41 | ...proof,
42 | state_init: account.walletStateInit,
43 | },
44 | };
45 |
46 | const response = await (
47 | await fetch(`${this.host}/ton-proof/checkProof`, {
48 | method: 'POST',
49 | body: JSON.stringify(reqBody),
50 | })
51 | ).json();
52 |
53 | if (response?.token) {
54 | localStorage.setItem(this.localStorageKey, response.token);
55 | this.accessToken = response.token;
56 | }
57 | } catch (e) {
58 | console.log('checkProof error:', e);
59 | }
60 | }
61 |
62 | async getAccountInfo(account: Account) {
63 | const response = await (
64 | await fetch(`${this.host}/dapp/getAccountInfo?network=${account.chain}`, {
65 | headers: {
66 | Authorization: `Bearer ${this.accessToken}`,
67 | 'Content-Type': 'application/json',
68 | },
69 | })
70 | ).json();
71 |
72 | return response as {};
73 | }
74 |
75 | reset() {
76 | this.accessToken = null;
77 | localStorage.removeItem(this.localStorageKey);
78 | this.generatePayload();
79 | }
80 | }
81 |
82 | export const TonProofDemoApi = new TonProofDemoApiService();
83 |
--------------------------------------------------------------------------------
/src/components/TxForm/TxForm.tsx:
--------------------------------------------------------------------------------
1 | import React, {useCallback, useState} from 'react';
2 | import ReactJson from 'react-json-view';
3 | import './style.scss';
4 | import {SendTransactionRequest, useTonConnectUI, useTonWallet} from "@tonconnect/ui-react";
5 |
6 | // In this example, we are using a predefined smart contract state initialization (`stateInit`)
7 | // to interact with an "EchoContract". This contract is designed to send the value back to the sender,
8 | // serving as a testing tool to prevent users from accidentally spending money.
9 | const defaultTx: SendTransactionRequest = {
10 | // The transaction is valid for 10 minutes from now, in unix epoch seconds.
11 | validUntil: Math.floor(Date.now() / 1000) + 600,
12 | messages: [
13 |
14 | {
15 | // The receiver's address.
16 | address: 'EQCKWpx7cNMpvmcN5ObM5lLUZHZRFKqYA4xmw9jOry0ZsF9M',
17 | // Amount to send in nanoTON. For example, 0.005 TON is 5000000 nanoTON.
18 | amount: '5000000',
19 | // (optional) State initialization in boc base64 format.
20 | stateInit: 'te6cckEBBAEAOgACATQCAQAAART/APSkE/S88sgLAwBI0wHQ0wMBcbCRW+D6QDBwgBDIywVYzxYh+gLLagHPFsmAQPsAlxCarA==',
21 | // (optional) Payload in boc base64 format.
22 | payload: 'te6ccsEBAQEADAAMABQAAAAASGVsbG8hCaTc/g==',
23 | },
24 |
25 | // Uncomment the following message to send two messages in one transaction.
26 | /*
27 | {
28 | // Note: Funds sent to this address will not be returned back to the sender.
29 | address: 'UQAuz15H1ZHrZ_psVrAra7HealMIVeFq0wguqlmFno1f3B-m',
30 | amount: toNano('0.01').toString(),
31 | }
32 | */
33 |
34 | ],
35 | };
36 |
37 | export function TxForm() {
38 | const [tx, setTx] = useState(defaultTx);
39 | const wallet = useTonWallet();
40 | const [tonConnectUi] = useTonConnectUI();
41 |
42 | const onChange = useCallback((value: object) => setTx((value as { updated_src: typeof defaultTx }).updated_src), []);
43 |
44 | return (
45 |
46 |
Configure and send transaction
47 |
48 | {wallet ? (
49 |
52 | ) : (
53 |
54 | )}
55 |
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/src/components/TonProofDemo/TonProofDemo.tsx:
--------------------------------------------------------------------------------
1 | import React, {useCallback, useEffect, useRef, useState} from 'react';
2 | import ReactJson from 'react-json-view';
3 | import './style.scss';
4 | import {TonProofDemoApi} from "../../TonProofDemoApi";
5 | import {useTonConnectUI, useTonWallet} from "@tonconnect/ui-react";
6 | import {CHAIN} from "@tonconnect/ui-react";
7 | import useInterval from "../../hooks/useInterval";
8 |
9 |
10 | export const TonProofDemo = () => {
11 | const firstProofLoading = useRef(true);
12 |
13 | const [data, setData] = useState({});
14 | const wallet = useTonWallet();
15 | const [authorized, setAuthorized] = useState(false);
16 | const [tonConnectUI] = useTonConnectUI();
17 |
18 | const recreateProofPayload = useCallback(async () => {
19 | if (firstProofLoading.current) {
20 | tonConnectUI.setConnectRequestParameters({ state: 'loading' });
21 | firstProofLoading.current = false;
22 | }
23 |
24 | const payload = await TonProofDemoApi.generatePayload();
25 |
26 | if (payload) {
27 | tonConnectUI.setConnectRequestParameters({ state: 'ready', value: payload });
28 | } else {
29 | tonConnectUI.setConnectRequestParameters(null);
30 | }
31 | }, [tonConnectUI, firstProofLoading])
32 |
33 | if (firstProofLoading.current) {
34 | recreateProofPayload();
35 | }
36 |
37 | useInterval(recreateProofPayload, TonProofDemoApi.refreshIntervalMs);
38 |
39 | useEffect(() =>
40 | tonConnectUI.onStatusChange(async w => {
41 | if (!w || w.account.chain === CHAIN.TESTNET) {
42 | TonProofDemoApi.reset();
43 | setAuthorized(false);
44 | return;
45 | }
46 |
47 | if (w.connectItems?.tonProof && 'proof' in w.connectItems.tonProof) {
48 | await TonProofDemoApi.checkProof(w.connectItems.tonProof.proof, w.account);
49 | }
50 |
51 | if (!TonProofDemoApi.accessToken) {
52 | tonConnectUI.disconnect();
53 | setAuthorized(false);
54 | return;
55 | }
56 |
57 | setAuthorized(true);
58 | }), [tonConnectUI]);
59 |
60 |
61 | const handleClick = useCallback(async () => {
62 | if (!wallet) {
63 | return;
64 | }
65 | const response = await TonProofDemoApi.getAccountInfo(wallet.account);
66 |
67 | setData(response);
68 | }, [wallet]);
69 |
70 | if (!authorized) {
71 | return null;
72 | }
73 |
74 | return (
75 |
76 |
Demo backend API with ton_proof verification
77 | {authorized ? (
78 |
81 | ) : (
82 |
Connect wallet to call API
83 | )}
84 |
85 |
86 | );
87 | }
88 |
--------------------------------------------------------------------------------
/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/Footer/ColorsSelect/ColorsSelect.tsx:
--------------------------------------------------------------------------------
1 | import {ColorsSet, THEME, useTonConnectUI} from "@tonconnect/ui-react";
2 | import {FunctionComponent, useEffect, useState} from "react";
3 | import './style.scss';
4 |
5 | const defaultColors = {
6 | [THEME.LIGHT]: {
7 | constant: {
8 | black: '#000000',
9 | white: '#FFFFFF'
10 | },
11 | connectButton: {
12 | background: '#0098EA',
13 | foreground: '#FFFFFF'
14 | },
15 | accent: '#0098EA',
16 | telegramButton: '#0098EA',
17 | icon: {
18 | primary: '#0F0F0F',
19 | secondary: '#7A8999',
20 | tertiary: '#C1CAD2',
21 | success: '#29CC6A',
22 | error: '#F5A73B'
23 | },
24 | background: {
25 | primary: '#FFFFFF',
26 | secondary: '#F1F3F5',
27 | segment: '#FFFFFF',
28 | tint: '#F1F3F5',
29 | qr: '#F1F3F5'
30 | },
31 | text: {
32 | primary: '#0F0F0F',
33 | secondary: '#6A7785'
34 | }
35 | },
36 | [THEME.DARK]: {
37 | constant: {
38 | black: '#000000',
39 | white: '#FFFFFF'
40 | },
41 | connectButton: {
42 | background: '#0098EA',
43 | foreground: '#FFFFFF'
44 | },
45 | accent: '#E5E5EA',
46 | telegramButton: '#31A6F5',
47 | icon: {
48 | primary: '#E5E5EA',
49 | secondary: '#909099',
50 | tertiary: '#434347',
51 | success: '#29CC6A',
52 | error: '#F5A73B'
53 | },
54 | background: {
55 | primary: '#121214',
56 | secondary: '#18181A',
57 | segment: '#262629',
58 | tint: '#222224',
59 | qr: '#F1F3F5'
60 | },
61 | text: {
62 | primary: '#E5E5EA',
63 | secondary: '#7D7D85'
64 | }
65 | }
66 | }
67 |
68 | export interface ColorsSelectProps {
69 | theme: THEME;
70 | }
71 | export const ColorsSelect: FunctionComponent = ({ theme }) => {
72 | const [_, setOptions] = useTonConnectUI();
73 | const [colors, setColors] = useState(defaultColors[theme]);
74 |
75 | useEffect(() => {
76 | setColors(defaultColors[theme]);
77 | }, [theme]);
78 |
79 | const onChange = (value: string, property1: string, property2?: string) => {
80 | setOptions({
81 | uiPreferences: {
82 | colorsSet: {
83 | [theme]: {
84 | [property1]: property2 ? {
85 | ...(colors as any)[property1],
86 | [property2]: value
87 | } : value
88 | }
89 | }
90 | }
91 | })
92 |
93 |
94 | setColors(colors => ({
95 | ...colors,
96 | [property1]: property2 ? {
97 | ...(colors as any)[property1],
98 | [property2]: value
99 | } : value
100 | }));
101 |
102 | defaultColors[theme] = {
103 | ...defaultColors[theme],
104 | [property1]: property2 ? {
105 | ...(colors as any)[property1],
106 | [property2]: value
107 | } : value
108 | }
109 | }
110 |
111 | return
146 | }
147 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Demo dApp with @tonconnect/ui-react
2 |
3 | This Demo dApp showcases the integration with @tonconnect/ui-react. Experience it live at [Demo dApp with Wallet](https://ton-connect.github.io/demo-dapp-with-wallet/).
4 |
5 | ## Learn More About Ton Connect
6 |
7 | To understand more about Ton Connect and how it enables blockchain functionalities in your applications, refer to the following resources:
8 | - Ton Connect Documentation: [https://docs.ton.org/develop/dapps/ton-connect/](https://docs.ton.org/develop/dapps/ton-connect/)
9 | - Ton Connect SDK and UI Library on GitHub: [https://github.com/ton-connect/sdk/tree/main/packages/ui](https://github.com/ton-connect/sdk/tree/main/packages/ui)
10 |
11 | ## Installation
12 |
13 | ### Project Dependencies
14 |
15 | Install the necessary packages for this project:
16 |
17 | ```bash
18 | npm install
19 | ```
20 |
21 | ### ngrok or localtunnel (Optional)
22 |
23 | Choose either ngrok or localtunnel to expose your local server to the internet for testing in Telegram.
24 |
25 | #### ngrok Installation
26 |
27 | ```bash
28 | npm install -g ngrok
29 | ```
30 |
31 | ngrok Documentation: [https://ngrok.com/docs](https://ngrok.com/docs)
32 |
33 | #### localtunnel Installation
34 |
35 | ```bash
36 | npm install -g localtunnel
37 | ```
38 |
39 | LocalTunnel Documentation: [https://localtunnel.github.io/www/](https://localtunnel.github.io/www/)
40 |
41 | ### Creating Telegram Mini Apps (Optional)
42 |
43 | 1. Open [@BotFather](https://t.me/BotFather) in Telegram.
44 | 2. Send the `/newbot` command to create a new bot.
45 | 3. Follow the prompts to set up your bot, providing all necessary information.
46 | 4. After the bot is created, send the `/newapp` command to BotFather.
47 | 5. Select your bot from the list.
48 | 6. Provide all the required information for your Mini App.
49 |
50 |
51 | ### Returning to the Application (Optional)
52 |
53 | To return to the application after interacting with the wallet, you must specify a `twaReturnUrl` in `src/App.tsx`.
54 |
55 | Here's a concise guide:
56 |
57 | - **twaReturnUrl**: This is the return URL used by Telegram Web Apps. Set it to redirect users back to your application after wallet interaction. Example: `'https://t.me/WebAppWalletBot/myapp'`.
58 |
59 | Here is a sample configuration for specifying a return URL:
60 |
61 | ```jsx
62 |
69 | ```
70 |
71 | ### Adding a Custom Wallet (Optional)
72 |
73 | To integrate a custom wallet into your application, adjust the `walletsListConfiguration` in `src/App.tsx`. Include your wallet details in `includeWallets` and specify `universalLink`.
74 |
75 | Here's a concise guide:
76 |
77 | - **universalLink**: This URL is used to open the wallet directly from a web link. It should link to your wallet's bot or app. Example: `'https://t.me/wallet/start'`.
78 |
79 | Here is a sample configuration for adding a custom wallet:
80 |
81 | ```jsx
82 |
102 | ```
103 |
104 | ## Running the Application
105 |
106 | ### Starting the Application
107 |
108 | To start the application, run:
109 |
110 | ```bash
111 | npm dev
112 | ```
113 |
114 | The application will be accessible at [http://localhost:5173](http://localhost:5173).
115 |
116 | ### Exposing Your Local Server (Optional)
117 |
118 | #### Using ngrok
119 |
120 | ```bash
121 | ngrok http 5173
122 | ```
123 |
124 | #### Using localtunnel
125 |
126 | ```bash
127 | lt --port 5173
128 | ```
129 |
130 | After setting up ngrok or localtunnel, update your Telegram bot's configuration with the provided URL to ensure the bot points to your local development environment.
131 |
132 | ### Updating Telegram Bot Configuration (Optional)
133 |
134 | #### Update the Menu Button URL in Telegram Bot
135 |
136 | 1. Open [@BotFather](https://t.me/BotFather) in Telegram.
137 | 2. Send the `/mybots` command and select your bot.
138 | 3. Choose "Bot Settings" then "Menu Button" and finally "Configure menu button".
139 | 4. Enter the ngrok or localtunnel URL as the new destination.
140 |
141 | #### Update Mini Apps URL in Telegram
142 |
143 | 1. Open [@BotFather](https://t.me/BotFather) in Telegram.
144 | 2. Send the `/myapps` command and select your Mini App.
145 | 3. Choose "Edit Web App URL".
146 | 4. Enter the ngrok or localtunnel URL as the new destination.
147 |
--------------------------------------------------------------------------------
/src/components/Footer/Footer.tsx:
--------------------------------------------------------------------------------
1 | import {BorderRadius, Locales, ReturnStrategy, Theme, THEME, useTonConnectUI} from "@tonconnect/ui-react";
2 | import './footer.scss';
3 | import {useEffect, useState} from "react";
4 | import {ColorsModal} from "./ColorsModal/ColorsModal";
5 |
6 | const defaultWalletsSelectValue = '["Tonkeeper", "OpenMask"]';
7 |
8 | export const Footer = () => {
9 | const [checkboxes, setCheckboxes] = useState(
10 | [true, false, false, true, true, true]
11 | );
12 |
13 | const [returnStrategy, setReturnStrategy] = useState('back');
14 | const [skipRedirect, setSkipRedirect] = useState('ios');
15 |
16 | const [_, setOptions] = useTonConnectUI();
17 |
18 | const onLangChange = (lang: string) => {
19 | setOptions({language: lang as Locales})
20 | }
21 |
22 | const onThemeChange = (theme: string) => {
23 | setOptions({uiPreferences: {theme: theme as Theme}})
24 | }
25 |
26 | const onBordersChange = (borders: string) => {
27 | setOptions({uiPreferences: {borderRadius: borders as BorderRadius}})
28 | }
29 |
30 | const onCheckboxChange = (position: number) => {
31 | setCheckboxes(state => state.map((item, index) => index === position ? !item : item ));
32 | }
33 |
34 | const onReturnStrategyInputBlur = () => {
35 | if (!returnStrategy) {
36 | setReturnStrategy('back');
37 | return;
38 | }
39 |
40 | setOptions({ actionsConfiguration: { returnStrategy: returnStrategy as ReturnStrategy } })
41 |
42 | }
43 |
44 | const onSkipRedirectInputBlur = () => {
45 | if (!skipRedirect) {
46 | setSkipRedirect('ios');
47 | return;
48 | }
49 |
50 | setOptions({ actionsConfiguration: { skipRedirectToWallet: skipRedirect as 'ios' | 'never' | 'always' } })
51 |
52 | }
53 |
54 | useEffect(() => {
55 | const actionValues = ['before', 'success', 'error'];
56 | const modals = actionValues.map((item, index) => checkboxes[index] ? item : undefined).filter(i => i) as ("before" | "success" | "error")[];
57 | const notifications = actionValues.map((item, index) => checkboxes[index + 3] ? item : undefined).filter(i => i) as ("before" | "success" | "error")[];
58 |
59 | setOptions({ actionsConfiguration: { modals, notifications } })
60 | }, [checkboxes])
61 |
62 | return
152 | }
153 |
--------------------------------------------------------------------------------