├── 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 |
16 | setTheme(THEME.LIGHT)}>LIGHT 17 | setTheme(THEME.DARK)}>DARK 18 |
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
112 | { Object.entries(colors).map(([key1, value1]) => { 113 | if (typeof value1 === 'object') { 114 | return
115 | {key1}: 116 | { 117 | Object.entries(value1).map(([key2, value2]) => 118 | 126 | ) 127 | } 128 | 129 |
130 | } 131 | 132 | return
133 | {key1}: 134 | 141 |
142 | 143 | }) 144 | } 145 |
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 | --------------------------------------------------------------------------------