├── .eslintrc.json ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── next.config.js ├── package.json ├── postcss.config.js ├── prisma └── schema.prisma ├── public └── assets │ ├── icon │ └── outline │ │ ├── back.svg │ │ ├── check.svg │ │ ├── close.svg │ │ ├── copy.svg │ │ ├── home.svg │ │ ├── inscribe.svg │ │ ├── orders.svg │ │ ├── qrcode.svg │ │ ├── setting.svg │ │ └── wallet.svg │ ├── loading.svg │ ├── loading2.svg │ ├── next.svg │ ├── okx.svg │ ├── telegram-web-app.js │ ├── telegram-widget.js │ ├── telegram.svg │ └── vercel.svg ├── src ├── api │ ├── brc20.ts │ ├── chain.ts │ └── mint.ts ├── app │ ├── [lang] │ │ ├── inscribe │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── orders │ │ │ └── page.tsx │ │ ├── page.tsx │ │ └── wallet │ │ │ └── page.tsx │ ├── api │ │ ├── brc20 │ │ │ ├── inscribe │ │ │ │ └── route.ts │ │ │ ├── mint │ │ │ │ ├── paid │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ ├── orders │ │ │ │ └── route.ts │ │ │ ├── tasks │ │ │ │ ├── create │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ └── tick │ │ │ │ ├── [tick] │ │ │ │ └── route.ts │ │ │ │ └── list │ │ │ │ └── route.ts │ │ └── telegram │ │ │ └── validate │ │ │ └── route.ts │ ├── favicon.ico │ └── globals.css ├── components │ ├── Brc20Minter │ │ ├── SendModal.tsx │ │ ├── SpeedItem.tsx │ │ ├── index.tsx │ │ └── useMint.ts │ ├── HomeView │ │ ├── MintButton.tsx │ │ └── index.tsx │ ├── InitApp │ │ └── index.tsx │ ├── LanguageChanger │ │ └── index.tsx │ ├── Login │ │ └── index.tsx │ ├── Navigator │ │ └── index.tsx │ ├── OrderList │ │ ├── TaskDisplay.tsx │ │ ├── WalletSelectModal.tsx │ │ ├── index.tsx │ │ └── useOrders.ts │ ├── TransactionConfirm │ │ └── index.tsx │ ├── TranslationsProvider │ │ └── index.tsx │ └── WalletManager │ │ ├── ConfirmMnemonic.tsx │ │ ├── CreateOrRestoreWallet.tsx │ │ ├── Mnemonic.tsx │ │ ├── ReceiveModal.tsx │ │ ├── RestoreMnemonic.tsx │ │ ├── SelectSource.tsx │ │ ├── SendModal.tsx │ │ ├── SetPassword.tsx │ │ ├── ViewMnemonicModal.tsx │ │ ├── WalletOperator.tsx │ │ └── index.tsx ├── hooks │ ├── useCopy.ts │ ├── useLatest.ts │ ├── useLoading.ts │ ├── useLocalstorage.ts │ ├── useNetwork.ts │ ├── useTgInitData.ts │ ├── useThrottleFn.ts │ ├── useToast.ts │ └── useWallet.ts ├── i18n-config.ts ├── locales │ ├── en.json │ ├── en │ │ ├── common.json │ │ └── home.json │ ├── initI18n.ts │ ├── zh-CN.json │ └── zh-CN │ │ ├── common.json │ │ └── home.json ├── middleware.ts ├── server │ └── btc.ts ├── types │ └── wallet.ts ├── ui │ ├── Button │ │ └── index.tsx │ ├── LoadingModal │ │ └── index.tsx │ └── Modal │ │ └── index.tsx └── utils │ ├── address.ts │ ├── browser-passworder.ts │ ├── etc.ts │ ├── formater.ts │ ├── mint.ts │ ├── transaction.ts │ └── unibabel.ts ├── tailwind.config.ts ├── tsconfig.json └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.inlineSuggest.showToolbar": "onHover", 3 | "svg.preview.background": "white" 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "brc20-inscribe-bot", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "postinstall": "prisma generate", 7 | "dev": "PORT=4987 next dev", 8 | "build": "next build", 9 | "start": "next start", 10 | "lint": "next lint" 11 | }, 12 | "dependencies": { 13 | "@cmdcode/crypto-utils": "^2.4.6", 14 | "@cmdcode/tapscript": "^1.4.3", 15 | "@formatjs/intl-localematcher": "^0.5.2", 16 | "@headlessui/react": "^1.7.17", 17 | "@prisma/client": "^5.7.0", 18 | "@scure/bip32": "^1.3.2", 19 | "@vercel/postgres": "^0.5.1", 20 | "bip39": "^3.1.0", 21 | "bitcoinjs-lib": "^6.1.5", 22 | "browser-passworder": "^2.0.3", 23 | "buffer": "^6.0.3", 24 | "copy-to-clipboard": "^3.3.3", 25 | "crypto-js": "^4.2.0", 26 | "daisyui": "^4.4.17", 27 | "ecpair": "^2.1.0", 28 | "i18next": "^23.7.7", 29 | "i18next-resources-to-backend": "^1.2.0", 30 | "negotiator": "^0.6.3", 31 | "next": "14.0.3", 32 | "next-i18n-router": "^5.0.2", 33 | "node-fetch": "^3.3.2", 34 | "prisma": "^5.7.0", 35 | "qrcode.react": "^3.1.0", 36 | "react": "^18", 37 | "react-dom": "^18", 38 | "react-hot-toast": "^2.4.1", 39 | "react-i18next": "^13.5.0", 40 | "react-svg": "^16.1.31", 41 | "server-only": "^0.0.1", 42 | "tailwind-merge": "^2.0.0", 43 | "uuid": "^9.0.1" 44 | }, 45 | "devDependencies": { 46 | "@types/crypto-js": "^4.2.1", 47 | "@types/negotiator": "^0.6.3", 48 | "@types/node": "^20", 49 | "@types/react": "^18", 50 | "@types/react-dom": "^18", 51 | "@types/uuid": "^9.0.7", 52 | "autoprefixer": "^10.0.1", 53 | "eslint": "^8", 54 | "eslint-config-next": "14.0.3", 55 | "postcss": "^8", 56 | "tailwindcss": "^3.3.0", 57 | "typescript": "^5" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | provider = "postgresql" 7 | url = env("POSTGRES_PRISMA_URL") 8 | } 9 | 10 | model inscribe_text_tasks { 11 | id String @id @db.VarChar(255) 12 | user_id String? @db.VarChar(255) 13 | secret String? @db.VarChar(255) 14 | text String? 15 | receive_address String? @db.VarChar(255) 16 | inscribe_address String? @db.VarChar(255) 17 | created_at DateTime? @db.Timestamp(6) 18 | updated_at DateTime? @db.Timestamp(6) 19 | status String? @db.VarChar(50) 20 | } 21 | -------------------------------------------------------------------------------- /public/assets/icon/outline/back.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/icon/outline/check.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/icon/outline/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/icon/outline/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/assets/icon/outline/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/icon/outline/inscribe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/icon/outline/orders.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/icon/outline/qrcode.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /public/assets/icon/outline/setting.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/icon/outline/wallet.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/loading.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /public/assets/loading2.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 13 | 14 | 16 | 24 | 25 | 27 | 35 | 36 | -------------------------------------------------------------------------------- /public/assets/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/okx.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 25 | 36 | 47 | 58 | 59 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /public/assets/telegram-widget.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | (function(window){ 3 | window.__parseFunction = function(__func, __attrs) { 4 | __attrs = __attrs || []; 5 | __func = '(function(' + __attrs.join(',') + '){' + __func + '})'; 6 | return window.execScript ? window.execScript(__func) : eval(__func); 7 | } 8 | }(window)); 9 | (function(window){ 10 | 11 | function addEvent(el, event, handler) { 12 | var events = event.split(/\s+/); 13 | for (var i = 0; i < events.length; i++) { 14 | if (el.addEventListener) { 15 | el.addEventListener(events[i], handler); 16 | } else { 17 | el.attachEvent('on' + events[i], handler); 18 | } 19 | } 20 | } 21 | function removeEvent(el, event, handler) { 22 | var events = event.split(/\s+/); 23 | for (var i = 0; i < events.length; i++) { 24 | if (el.removeEventListener) { 25 | el.removeEventListener(events[i], handler); 26 | } else { 27 | el.detachEvent('on' + events[i], handler); 28 | } 29 | } 30 | } 31 | function getCssProperty(el, prop) { 32 | if (window.getComputedStyle) { 33 | return window.getComputedStyle(el, '').getPropertyValue(prop) || null; 34 | } else if (el.currentStyle) { 35 | return el.currentStyle[prop] || null; 36 | } 37 | return null; 38 | } 39 | function geById(el_or_id) { 40 | if (typeof el_or_id == 'string' || el_or_id instanceof String) { 41 | return document.getElementById(el_or_id); 42 | } else if (el_or_id instanceof HTMLElement) { 43 | return el_or_id; 44 | } 45 | return null; 46 | } 47 | 48 | var getWidgetsOrigin = function(default_origin, dev_origin) { 49 | var link = document.createElement('A'), origin; 50 | link.href = document.currentScript && document.currentScript.src || default_origin; 51 | origin = link.origin || link.protocol + '//' + link.hostname; 52 | if (origin == 'https://telegram.org') { 53 | origin = default_origin; 54 | } else if (origin == 'https://telegram-js.azureedge.net' || origin == 'https://tg.dev') { 55 | origin = dev_origin; 56 | } 57 | return origin; 58 | }; 59 | 60 | var getPageCanonical = function() { 61 | var a = document.createElement('A'), link, href; 62 | if (document.querySelector) { 63 | link = document.querySelector('link[rel="canonical"]'); 64 | if (link && (href = link.getAttribute('href'))) { 65 | a.href = href; 66 | return a.href; 67 | } 68 | } else { 69 | var links = document.getElementsByTagName('LINK'); 70 | for (var i = 0; i < links.length; i++) { 71 | if ((link = links[i]) && 72 | (link.getAttribute('rel') == 'canonical') && 73 | (href = link.getAttribute('href'))) { 74 | a.href = href; 75 | return a.href; 76 | } 77 | } 78 | } 79 | return false; 80 | }; 81 | 82 | function haveTgAuthResult() { 83 | var locationHash = '', re = /[#\?\&]tgAuthResult=([A-Za-z0-9\-_=]*)$/, match; 84 | try { 85 | locationHash = location.hash.toString(); 86 | if (match = locationHash.match(re)) { 87 | location.hash = locationHash.replace(re, ''); 88 | var data = match[1] || ''; 89 | data = data.replace(/-/g, '+').replace(/_/g, '/'); 90 | var pad = data.length % 4; 91 | if (pad > 1) { 92 | data += new Array(5 - pad).join('='); 93 | } 94 | return JSON.parse(window.atob(data)); 95 | } 96 | } catch (e) {} 97 | return false; 98 | } 99 | 100 | function getXHR() { 101 | if (navigator.appName == "Microsoft Internet Explorer"){ 102 | return new ActiveXObject("Microsoft.XMLHTTP"); 103 | } else { 104 | return new XMLHttpRequest(); 105 | } 106 | } 107 | 108 | if (!window.Telegram) { 109 | window.Telegram = {}; 110 | } 111 | if (!window.Telegram.__WidgetUuid) { 112 | window.Telegram.__WidgetUuid = 0; 113 | } 114 | if (!window.Telegram.__WidgetLastId) { 115 | window.Telegram.__WidgetLastId = 0; 116 | } 117 | if (!window.Telegram.__WidgetCallbacks) { 118 | window.Telegram.__WidgetCallbacks = {}; 119 | } 120 | 121 | function postMessageToIframe(iframe, event, data, callback) { 122 | if (!iframe._ready) { 123 | if (!iframe._readyQueue) iframe._readyQueue = []; 124 | iframe._readyQueue.push([event, data, callback]); 125 | return; 126 | } 127 | try { 128 | data = data || {}; 129 | data.event = event; 130 | if (callback) { 131 | data._cb = ++window.Telegram.__WidgetLastId; 132 | window.Telegram.__WidgetCallbacks[data._cb] = { 133 | iframe: iframe, 134 | callback: callback 135 | }; 136 | } 137 | iframe.contentWindow.postMessage(JSON.stringify(data), '*'); 138 | } catch(e) {} 139 | } 140 | 141 | function initWidget(widgetEl) { 142 | var widgetId, widgetElId, widgetsOrigin, existsEl, 143 | src, styles = {}, allowedAttrs = [], 144 | defWidth, defHeight, scrollable = false, onInitAuthUser, onAuthUser, onUnauth; 145 | if (!widgetEl.tagName || 146 | !(widgetEl.tagName.toUpperCase() == 'SCRIPT' || 147 | widgetEl.tagName.toUpperCase() == 'BLOCKQUOTE' && 148 | widgetEl.classList.contains('telegram-post'))) { 149 | return null; 150 | } 151 | if (widgetEl._iframe) { 152 | return widgetEl._iframe; 153 | } 154 | if (widgetId = widgetEl.getAttribute('data-telegram-post')) { 155 | var comment = widgetEl.getAttribute('data-comment') || ''; 156 | widgetsOrigin = getWidgetsOrigin('https://t.me', 'https://post.tg.dev'); 157 | widgetElId = 'telegram-post-' + widgetId.replace(/[^a-z0-9_]/ig, '-') + (comment ? '-comment' + comment : ''); 158 | src = widgetsOrigin + '/' + widgetId + '?embed=1'; 159 | allowedAttrs = ['comment', 'userpic', 'mode', 'single?', 'color', 'dark', 'dark_color']; 160 | defWidth = widgetEl.getAttribute('data-width') || '100%'; 161 | defHeight = ''; 162 | styles.minWidth = '320px'; 163 | } 164 | else if (widgetId = widgetEl.getAttribute('data-telegram-discussion')) { 165 | widgetsOrigin = getWidgetsOrigin('https://t.me', 'https://post.tg.dev'); 166 | widgetElId = 'telegram-discussion-' + widgetId.replace(/[^a-z0-9_]/ig, '-') + '-' + (++window.Telegram.__WidgetUuid); 167 | var websitePageUrl = widgetEl.getAttribute('data-page-url'); 168 | if (!websitePageUrl) { 169 | websitePageUrl = getPageCanonical(); 170 | } 171 | src = widgetsOrigin + '/' + widgetId + '?embed=1&discussion=1' + (websitePageUrl ? '&page_url=' + encodeURIComponent(websitePageUrl) : ''); 172 | allowedAttrs = ['comments_limit', 'color', 'colorful', 'dark', 'dark_color', 'width', 'height']; 173 | defWidth = widgetEl.getAttribute('data-width') || '100%'; 174 | defHeight = widgetEl.getAttribute('data-height') || 0; 175 | styles.minWidth = '320px'; 176 | if (defHeight > 0) { 177 | scrollable = true; 178 | } 179 | } 180 | else if (widgetEl.hasAttribute('data-telegram-login')) { 181 | widgetId = widgetEl.getAttribute('data-telegram-login'); 182 | widgetsOrigin = getWidgetsOrigin('https://oauth.telegram.org', 'https://oauth.tg.dev'); 183 | widgetElId = 'telegram-login-' + widgetId.replace(/[^a-z0-9_]/ig, '-'); 184 | src = widgetsOrigin + '/embed/' + widgetId + '?origin=' + encodeURIComponent(location.origin || location.protocol + '//' + location.hostname) + '&return_to=' + encodeURIComponent(location.href); 185 | allowedAttrs = ['size', 'userpic', 'init_auth', 'request_access', 'radius', 'min_width', 'max_width', 'lang']; 186 | defWidth = 186; 187 | defHeight = 28; 188 | if (widgetEl.hasAttribute('data-size')) { 189 | var size = widgetEl.getAttribute('data-size'); 190 | if (size == 'small') defWidth = 148, defHeight = 20; 191 | else if (size == 'large') defWidth = 238, defHeight = 40; 192 | } 193 | if (widgetEl.hasAttribute('data-onauth')) { 194 | onInitAuthUser = onAuthUser = __parseFunction(widgetEl.getAttribute('data-onauth'), ['user']); 195 | } 196 | else if (widgetEl.hasAttribute('data-auth-url')) { 197 | var a = document.createElement('A'); 198 | a.href = widgetEl.getAttribute('data-auth-url'); 199 | onAuthUser = function(user) { 200 | var authUrl = a.href; 201 | authUrl += (authUrl.indexOf('?') >= 0) ? '&' : '?'; 202 | var params = []; 203 | for (var key in user) { 204 | params.push(key + '=' + encodeURIComponent(user[key])); 205 | } 206 | authUrl += params.join('&'); 207 | location.href = authUrl; 208 | }; 209 | } 210 | if (widgetEl.hasAttribute('data-onunauth')) { 211 | onUnauth = __parseFunction(widgetEl.getAttribute('data-onunauth')); 212 | } 213 | var auth_result = haveTgAuthResult(); 214 | if (auth_result && onAuthUser) { 215 | onAuthUser(auth_result); 216 | } 217 | } 218 | else if (widgetId = widgetEl.getAttribute('data-telegram-share-url')) { 219 | widgetsOrigin = getWidgetsOrigin('https://t.me', 'https://post.tg.dev'); 220 | widgetElId = 'telegram-share-' + window.btoa(widgetId); 221 | src = widgetsOrigin + '/share/embed?origin=' + encodeURIComponent(location.origin || location.protocol + '//' + location.hostname); 222 | allowedAttrs = ['telegram-share-url', 'comment', 'size', 'text']; 223 | defWidth = 60; 224 | defHeight = 20; 225 | if (widgetEl.getAttribute('data-size') == 'large') { 226 | defWidth = 76; 227 | defHeight = 28; 228 | } 229 | } 230 | else { 231 | return null; 232 | } 233 | existsEl = document.getElementById(widgetElId); 234 | if (existsEl) { 235 | return existsEl; 236 | } 237 | for (var i = 0; i < allowedAttrs.length; i++) { 238 | var attr = allowedAttrs[i]; 239 | var novalue = attr.substr(-1) == '?'; 240 | if (novalue) { 241 | attr = attr.slice(0, -1); 242 | } 243 | var data_attr = 'data-' + attr.replace(/_/g, '-'); 244 | if (widgetEl.hasAttribute(data_attr)) { 245 | var attr_value = novalue ? '1' : encodeURIComponent(widgetEl.getAttribute(data_attr)); 246 | src += '&' + attr + '=' + attr_value; 247 | } 248 | } 249 | function getCurCoords(iframe) { 250 | var docEl = document.documentElement; 251 | var frect = iframe.getBoundingClientRect(); 252 | return { 253 | frameTop: frect.top, 254 | frameBottom: frect.bottom, 255 | frameLeft: frect.left, 256 | frameRight: frect.right, 257 | frameWidth: frect.width, 258 | frameHeight: frect.height, 259 | scrollTop: window.pageYOffset, 260 | scrollLeft: window.pageXOffset, 261 | clientWidth: docEl.clientWidth, 262 | clientHeight: docEl.clientHeight 263 | }; 264 | } 265 | function visibilityHandler() { 266 | if (isVisible(iframe, 50)) { 267 | postMessageToIframe(iframe, 'visible', {frame: widgetElId}); 268 | } 269 | } 270 | function focusHandler() { 271 | postMessageToIframe(iframe, 'focus', {has_focus: document.hasFocus()}); 272 | } 273 | function postMessageHandler(event) { 274 | if (event.source !== iframe.contentWindow || 275 | event.origin != widgetsOrigin) { 276 | return; 277 | } 278 | try { 279 | var data = JSON.parse(event.data); 280 | } catch(e) { 281 | var data = {}; 282 | } 283 | if (data.event == 'resize') { 284 | if (data.height) { 285 | iframe.style.height = data.height + 'px'; 286 | } 287 | if (data.width) { 288 | iframe.style.width = data.width + 'px'; 289 | } 290 | } 291 | else if (data.event == 'ready') { 292 | iframe._ready = true; 293 | focusHandler(); 294 | for (var i = 0; i < iframe._readyQueue.length; i++) { 295 | var queue_item = iframe._readyQueue[i]; 296 | postMessageToIframe(iframe, queue_item[0], queue_item[1], queue_item[2]); 297 | } 298 | iframe._readyQueue = []; 299 | } 300 | else if (data.event == 'visible_off') { 301 | removeEvent(window, 'scroll', visibilityHandler); 302 | removeEvent(window, 'resize', visibilityHandler); 303 | } 304 | else if (data.event == 'get_coords') { 305 | postMessageToIframe(iframe, 'callback', { 306 | _cb: data._cb, 307 | value: getCurCoords(iframe) 308 | }); 309 | } 310 | else if (data.event == 'scroll_to') { 311 | try { 312 | window.scrollTo(data.x || 0, data.y || 0); 313 | } catch(e) {} 314 | } 315 | else if (data.event == 'auth_user') { 316 | if (data.init) { 317 | onInitAuthUser && onInitAuthUser(data.auth_data); 318 | } else { 319 | onAuthUser && onAuthUser(data.auth_data); 320 | } 321 | } 322 | else if (data.event == 'unauthorized') { 323 | onUnauth && onUnauth(); 324 | } 325 | else if (data.event == 'callback') { 326 | var cb_data = null; 327 | if (cb_data = window.Telegram.__WidgetCallbacks[data._cb]) { 328 | if (cb_data.iframe === iframe) { 329 | cb_data.callback(data.value); 330 | delete window.Telegram.__WidgetCallbacks[data._cb]; 331 | } 332 | } else { 333 | console.warn('Callback #' + data._cb + ' not found'); 334 | } 335 | } 336 | } 337 | var iframe = document.createElement('iframe'); 338 | iframe.id = widgetElId; 339 | iframe.src = src; 340 | iframe.width = defWidth; 341 | iframe.height = defHeight; 342 | iframe.setAttribute('frameborder', '0'); 343 | if (!scrollable) { 344 | iframe.setAttribute('scrolling', 'no'); 345 | iframe.style.overflow = 'hidden'; 346 | } 347 | iframe.style.colorScheme = 'light dark'; 348 | iframe.style.border = 'none'; 349 | for (var prop in styles) { 350 | iframe.style[prop] = styles[prop]; 351 | } 352 | if (widgetEl.parentNode) { 353 | widgetEl.parentNode.insertBefore(iframe, widgetEl); 354 | if (widgetEl.tagName.toUpperCase() == 'BLOCKQUOTE') { 355 | widgetEl.parentNode.removeChild(widgetEl); 356 | } 357 | } 358 | iframe._ready = false; 359 | iframe._readyQueue = []; 360 | widgetEl._iframe = iframe; 361 | addEvent(iframe, 'load', function() { 362 | removeEvent(iframe, 'load', visibilityHandler); 363 | addEvent(window, 'scroll', visibilityHandler); 364 | addEvent(window, 'resize', visibilityHandler); 365 | visibilityHandler(); 366 | }); 367 | addEvent(window, 'focus blur', focusHandler); 368 | addEvent(window, 'message', postMessageHandler); 369 | return iframe; 370 | } 371 | function isVisible(el, padding) { 372 | var node = el, val; 373 | var visibility = getCssProperty(node, 'visibility'); 374 | if (visibility == 'hidden') return false; 375 | while (node) { 376 | if (node === document.documentElement) break; 377 | var display = getCssProperty(node, 'display'); 378 | if (display == 'none') return false; 379 | var opacity = getCssProperty(node, 'opacity'); 380 | if (opacity !== null && opacity < 0.1) return false; 381 | node = node.parentNode; 382 | } 383 | if (el.getBoundingClientRect) { 384 | padding = +padding || 0; 385 | var rect = el.getBoundingClientRect(); 386 | var html = document.documentElement; 387 | if (rect.bottom < padding || 388 | rect.right < padding || 389 | rect.top > (window.innerHeight || html.clientHeight) - padding || 390 | rect.left > (window.innerWidth || html.clientWidth) - padding) { 391 | return false; 392 | } 393 | } 394 | return true; 395 | } 396 | 397 | function getAllWidgets() { 398 | var widgets = []; 399 | if (document.querySelectorAll) { 400 | widgets = document.querySelectorAll('script[data-telegram-post],blockquote.telegram-post,script[data-telegram-discussion],script[data-telegram-login],script[data-telegram-share-url]'); 401 | } else { 402 | widgets = Array.prototype.slice.apply(document.getElementsByTagName('SCRIPT')); 403 | widgets = widgets.concat(Array.prototype.slice.apply(document.getElementsByTagName('BLOCKQUOTE'))); 404 | } 405 | return widgets; 406 | } 407 | 408 | function getWidgetInfo(el_or_id, callback) { 409 | var e = null, iframe = null; 410 | if (el = geById(el_or_id)) { 411 | if (el.tagName && 412 | el.tagName.toUpperCase() == 'IFRAME') { 413 | iframe = el; 414 | } else if (el._iframe) { 415 | iframe = el._iframe; 416 | } 417 | if (iframe && callback) { 418 | postMessageToIframe(iframe, 'get_info', {}, callback); 419 | } 420 | } 421 | } 422 | 423 | function setWidgetOptions(options, el_or_id) { 424 | var e = null, iframe = null; 425 | if (typeof el_or_id === 'undefined') { 426 | var widgets = getAllWidgets(); 427 | for (var i = 0; i < widgets.length; i++) { 428 | if (iframe = widgets[i]._iframe) { 429 | postMessageToIframe(iframe, 'set_options', {options: options}); 430 | } 431 | } 432 | } else { 433 | if (el = geById(el_or_id)) { 434 | if (el.tagName && 435 | el.tagName.toUpperCase() == 'IFRAME') { 436 | iframe = el; 437 | } else if (el._iframe) { 438 | iframe = el._iframe; 439 | } 440 | if (iframe) { 441 | postMessageToIframe(iframe, 'set_options', {options: options}); 442 | } 443 | } 444 | } 445 | } 446 | 447 | if (!document.currentScript || 448 | !initWidget(document.currentScript)) { 449 | var widgets = getAllWidgets(); 450 | for (var i = 0; i < widgets.length; i++) { 451 | initWidget(widgets[i]); 452 | } 453 | } 454 | 455 | var TelegramLogin = { 456 | popups: {}, 457 | options: null, 458 | auth_callback: null, 459 | _init: function(options, auth_callback) { 460 | TelegramLogin.options = options; 461 | TelegramLogin.auth_callback = auth_callback; 462 | var auth_result = haveTgAuthResult(); 463 | if (auth_result && auth_callback) { 464 | auth_callback(auth_result); 465 | } 466 | }, 467 | _open: function(callback) { 468 | TelegramLogin._auth(TelegramLogin.options, function(authData) { 469 | if (TelegramLogin.auth_callback) { 470 | TelegramLogin.auth_callback(authData); 471 | } 472 | if (callback) { 473 | callback(authData); 474 | } 475 | }); 476 | }, 477 | _auth: function(options, callback) { 478 | var bot_id = parseInt(options.bot_id); 479 | if (!bot_id) { 480 | throw new Error('Bot id required'); 481 | } 482 | var width = 550; 483 | var height = 470; 484 | var left = Math.max(0, (screen.width - width) / 2) + (screen.availLeft | 0), 485 | top = Math.max(0, (screen.height - height) / 2) + (screen.availTop | 0); 486 | var onMessage = function (event) { 487 | try { 488 | var data = JSON.parse(event.data); 489 | } catch(e) { 490 | var data = {}; 491 | } 492 | if (!TelegramLogin.popups[bot_id]) return; 493 | if (event.source !== TelegramLogin.popups[bot_id].window) return; 494 | if (data.event == 'auth_result') { 495 | onAuthDone(data.result); 496 | } 497 | }; 498 | var onAuthDone = function (authData) { 499 | if (!TelegramLogin.popups[bot_id]) return; 500 | if (TelegramLogin.popups[bot_id].authFinished) return; 501 | callback && callback(authData); 502 | TelegramLogin.popups[bot_id].authFinished = true; 503 | removeEvent(window, 'message', onMessage); 504 | }; 505 | var checkClose = function(bot_id) { 506 | if (!TelegramLogin.popups[bot_id]) return; 507 | if (!TelegramLogin.popups[bot_id].window || 508 | TelegramLogin.popups[bot_id].window.closed) { 509 | return TelegramLogin.getAuthData(options, function(origin, authData) { 510 | onAuthDone(authData); 511 | }); 512 | } 513 | setTimeout(checkClose, 100, bot_id); 514 | } 515 | var popup_url = Telegram.Login.widgetsOrigin + '/auth?bot_id=' + encodeURIComponent(options.bot_id) + '&origin=' + encodeURIComponent(location.origin || location.protocol + '//' + location.hostname) + (options.request_access ? '&request_access=' + encodeURIComponent(options.request_access) : '') + (options.lang ? '&lang=' + encodeURIComponent(options.lang) : '') + '&return_to=' + encodeURIComponent(location.href); 516 | var popup = window.open(popup_url, 'telegram_oauth_bot' + bot_id, 'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top + ',status=0,location=0,menubar=0,toolbar=0'); 517 | TelegramLogin.popups[bot_id] = { 518 | window: popup, 519 | authFinished: false 520 | }; 521 | if (popup) { 522 | addEvent(window, 'message', onMessage); 523 | popup.focus(); 524 | checkClose(bot_id); 525 | } 526 | }, 527 | getAuthData: function(options, callback) { 528 | var bot_id = parseInt(options.bot_id); 529 | if (!bot_id) { 530 | throw new Error('Bot id required'); 531 | } 532 | var xhr = getXHR(); 533 | var url = Telegram.Login.widgetsOrigin + '/auth/get'; 534 | xhr.open('POST', url); 535 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); 536 | xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 537 | xhr.onreadystatechange = function() { 538 | if (xhr.readyState == 4) { 539 | if (typeof xhr.responseBody == 'undefined' && xhr.responseText) { 540 | try { 541 | var result = JSON.parse(xhr.responseText); 542 | } catch(e) { 543 | var result = {}; 544 | } 545 | if (result.user) { 546 | callback(result.origin, result.user); 547 | } else { 548 | callback(result.origin, false); 549 | } 550 | } else { 551 | callback('*', false); 552 | } 553 | } 554 | }; 555 | xhr.onerror = function() { 556 | callback('*', false); 557 | }; 558 | xhr.withCredentials = true; 559 | xhr.send('bot_id=' + encodeURIComponent(options.bot_id) + (options.lang ? '&lang=' + encodeURIComponent(options.lang) : '')); 560 | } 561 | }; 562 | 563 | window.Telegram.getWidgetInfo = getWidgetInfo; 564 | window.Telegram.setWidgetOptions = setWidgetOptions; 565 | window.Telegram.Login = { 566 | init: TelegramLogin._init, 567 | open: TelegramLogin._open, 568 | auth: TelegramLogin._auth, 569 | widgetsOrigin: getWidgetsOrigin('https://oauth.telegram.org', 'https://oauth.tg.dev') 570 | }; 571 | 572 | }(window)); 573 | })(window); -------------------------------------------------------------------------------- /public/assets/telegram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/assets/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/brc20.ts: -------------------------------------------------------------------------------- 1 | export const fetchTickList = async () => { 2 | const resp = await fetch(`/api/brc20/tick/list`, { 3 | method: "GET", 4 | headers: { 5 | "Content-Type": "application/json", 6 | }, 7 | }); 8 | const data = await resp.json(); 9 | return data?.data; 10 | } -------------------------------------------------------------------------------- /src/api/chain.ts: -------------------------------------------------------------------------------- 1 | import { UtxoInfo } from '@/types/wallet' 2 | 3 | export const fetchChainFeeRate = async (network: "main" | "testnet") => { 4 | const url = 5 | network === "main" 6 | ? "https://mempool.space/api/v1/fees/recommended" 7 | : "https://mempool.space/testnet/api/v1/fees/recommended"; 8 | const resp = await fetch(url); 9 | const data = await resp.json(); 10 | return data; 11 | }; 12 | 13 | interface AddressStatInfo { 14 | address: string; 15 | chain_stats: { 16 | funded_txo_count: number; 17 | funded_txo_sum: number; 18 | spent_txo_count: number; 19 | spent_txo_sum: number; 20 | tx_count: number; 21 | }; 22 | mempool_stats: { 23 | funded_txo_count: number; 24 | funded_txo_sum: number; 25 | spent_txo_count: number; 26 | spent_txo_sum: number; 27 | tx_count: number; 28 | }; 29 | } 30 | 31 | export const fetchChainBalance = async ( 32 | address: string, 33 | network: "main" | "testnet" 34 | ) => { 35 | const url = 36 | network === "main" 37 | ? `https://mempool.space/api/address/${address}` 38 | : `https://mempool.space/testnet/api/address/${address}`; 39 | const resp = await fetch(url); 40 | const data = (await resp.json()) as AddressStatInfo; 41 | return data; 42 | }; 43 | 44 | export const fetchChainTx = async ( 45 | txid: string, 46 | network: "main" | "testnet" 47 | ) => { 48 | const url = 49 | network === "main" 50 | ? `https://mempool.space/api/tx/${txid}` 51 | : `https://mempool.space/testnet/api/tx/${txid}`; 52 | const resp = await fetch(url); 53 | const data = await resp.json(); 54 | return data; 55 | }; 56 | 57 | export const fetchChainTxHex = async ( 58 | txid: string, 59 | network: "main" | "testnet" 60 | ) => { 61 | const url = 62 | network === "main" 63 | ? `https://mempool.space/api/tx/${txid}/hex` 64 | : `https://mempool.space/testnet/api/tx/${txid}/hex`; 65 | const resp = await fetch(url); 66 | const data = await resp.text(); 67 | return data; 68 | }; 69 | 70 | export const fetchChainTxList = async ( 71 | address: string, 72 | network: "main" | "testnet" 73 | ) => { 74 | const url = 75 | network === "main" 76 | ? `https://mempool.space/api/address/${address}/txs` 77 | : `https://mempool.space/testnet/api/address/${address}/txs`; 78 | const resp = await fetch(url); 79 | const data = await resp.json(); 80 | return data; 81 | }; 82 | 83 | export const fetchChainTxListByBlock = async ( 84 | block: number, 85 | network: "main" | "testnet" 86 | ) => { 87 | const url = 88 | network === "main" 89 | ? `https://mempool.space/api/block/${block}/txids` 90 | : `https://mempool.space/testnet/api/block/${block}/txids`; 91 | const resp = await fetch(url); 92 | const data = await resp.json(); 93 | return data; 94 | }; 95 | 96 | export const fetchChainBlock = async ( 97 | block: number, 98 | network: "main" | "testnet" 99 | ) => { 100 | const url = 101 | network === "main" 102 | ? `https://mempool.space/api/block/${block}` 103 | : `https://mempool.space/testnet/api/block/${block}`; 104 | const resp = await fetch(url); 105 | const data = await resp.json(); 106 | return data; 107 | }; 108 | 109 | 110 | export const fetchAddressUtxo = async ( 111 | address: string, 112 | network: "main" | "testnet" 113 | ) => { 114 | const url = 115 | network === "main" 116 | ? `https://mempool.space/api/address/${address}/utxo` 117 | : `https://mempool.space/testnet/api/address/${address}/utxo`; 118 | const resp = await fetch(url); 119 | const data = await resp.json() as UtxoInfo[]; 120 | return data; 121 | }; 122 | 123 | export const broadcastTx = async ( 124 | txHex: string, 125 | network: "main" | "testnet" 126 | ) => { 127 | const url = 128 | network === "main" 129 | ? `https://mempool.space/api/tx` 130 | : `https://mempool.space/testnet/api/tx`; 131 | const resp = await fetch(url, { 132 | method: "POST", 133 | body: txHex, 134 | }); 135 | const data = await resp.text(); 136 | return data; 137 | } -------------------------------------------------------------------------------- /src/api/mint.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from "uuid"; 2 | 3 | export const fetchBrc20MintInscriptionAddress = async ( 4 | tick: string, 5 | amt: number, 6 | receiveAddress: string 7 | ) => { 8 | const resp = await fetch("/api/brc20/mint", { 9 | method: "POST", 10 | body: JSON.stringify({ 11 | tick, 12 | amt, 13 | receiveAddress, 14 | }), 15 | headers: { 16 | "Content-Type": "application/json", 17 | }, 18 | }); 19 | const data = await resp.json(); 20 | return data?.data; 21 | }; 22 | 23 | export const createOrder = async ( 24 | priv: string, 25 | tick: string, 26 | amt: number, 27 | receiveAddress: string 28 | ) => { 29 | const resp = await fetch("/api/brc20/mint", { 30 | method: "POST", 31 | body: JSON.stringify({ 32 | priv, 33 | tick, 34 | amt, 35 | receiveAddress, 36 | }), 37 | headers: { 38 | "Content-Type": "application/json", 39 | }, 40 | }); 41 | const data = await resp.json(); 42 | return data?.data; 43 | } 44 | 45 | export const createTextInscriptionTask = async ( 46 | userId: string, 47 | secret: string, 48 | text: string, 49 | receiveAddress: string, 50 | inscribeAddress: string, 51 | status: string, 52 | ) => { 53 | const resp = await fetch("/api/brc20/tasks/create", { 54 | method: "POST", 55 | body: JSON.stringify({ 56 | id: uuidv4(), 57 | userId, 58 | secret, 59 | text, 60 | receiveAddress, 61 | inscribeAddress, 62 | status, 63 | createdAt: new Date(), 64 | updatedAt: new Date(), 65 | }), 66 | headers: { 67 | "Content-Type": "application/json", 68 | }, 69 | }); 70 | const data = await resp.json(); 71 | return data?.data; 72 | } 73 | 74 | export const fetchBrc20MintPaid = async ( 75 | taskId: string, 76 | txid: string, 77 | vout: number, 78 | amount: number 79 | ) => { 80 | const resp = await fetch("/api/brc20/mint/paid", { 81 | method: "POST", 82 | body: JSON.stringify({ 83 | taskId, 84 | txid, 85 | vout, 86 | amount, 87 | }), 88 | headers: { 89 | "Content-Type": "application/json", 90 | }, 91 | }); 92 | const data = await resp.json(); 93 | return data?.data; 94 | }; 95 | 96 | export const inscribeBrc20Mint = async ( 97 | secret: string, 98 | text: string, 99 | txid: string, 100 | vout: number, 101 | amount: number, 102 | receiveAddress: string, 103 | outputAmount: number, 104 | network: "main" | "testnet" 105 | ) => { 106 | const resp = await fetch("/api/brc20/inscribe", { 107 | method: "POST", 108 | body: JSON.stringify({ 109 | secret, 110 | text, 111 | txid, 112 | vout, 113 | amount, 114 | receiveAddress, 115 | network, 116 | outputAmount, 117 | }), 118 | headers: { 119 | "Content-Type": "application/json", 120 | }, 121 | }); 122 | const data = await resp.json(); 123 | return data?.data; 124 | }; 125 | 126 | 127 | export const fetchTickInfo = async (tick: string) => { 128 | const resp = await fetch(`/api/brc20/tick/${tick}`, { 129 | method: "GET", 130 | headers: { 131 | "Content-Type": "application/json", 132 | }, 133 | }); 134 | const data = await resp.json(); 135 | return data?.data; 136 | } -------------------------------------------------------------------------------- /src/app/[lang]/inscribe/page.tsx: -------------------------------------------------------------------------------- 1 | import Brc20Minter from "@/components/Brc20Minter"; 2 | import Navigator from "@/components/Navigator"; 3 | import initTranslations from "@/locales/initI18n"; 4 | import TranslationsProvider from "@/components/TranslationsProvider"; 5 | 6 | export default async function Inscribe({ params: { lang } }: any) { 7 | const i18nNamespaces = ["common"]; 8 | const { resources } = await initTranslations(lang, i18nNamespaces); 9 | return ( 10 | 15 |
16 |
17 | 18 |
19 | 20 |
21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/app/[lang]/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import Script from "next/script"; 4 | import "../globals.css"; 5 | import InitApp from "@/components/InitApp"; 6 | import { Toaster } from "react-hot-toast"; 7 | 8 | const inter = Inter({ subsets: ["latin"] }); 9 | 10 | export const metadata: Metadata = { 11 | title: "BRC20 Minter Tool", 12 | description: "Generated by create next app", 13 | }; 14 | 15 | export default function RootLayout({ 16 | children, 17 | params, 18 | }: { 19 | children: React.ReactNode; 20 | params: any; 21 | }) { 22 | return ( 23 | 24 | 25 | */} 73 | 74 | 75 | 76 | 77 | 78 | 79 | ); 80 | }; 81 | 82 | export default WalletSelectModal; 83 | -------------------------------------------------------------------------------- /src/components/OrderList/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import useLocalStorage from "@/hooks/useLocalstorage"; 4 | import React, { useState } from "react"; 5 | import TaskDisplay from "./TaskDisplay"; 6 | // import useOrders from "./useOrders"; 7 | // import WalletSelectModal from "./WalletSelectModal"; 8 | 9 | const OrderList: React.FC = () => { 10 | const [orderList] = useLocalStorage("orderList", []); 11 | 12 | return ( 13 |
14 | {orderList 15 | .filter((item) => Boolean(item.taskId)) 16 | .map((item, index) => ( 17 | 26 | ))} 27 |
28 | ); 29 | }; 30 | 31 | export default OrderList; 32 | -------------------------------------------------------------------------------- /src/components/OrderList/useOrders.ts: -------------------------------------------------------------------------------- 1 | import { useState, useCallback, useEffect } from 'react' 2 | 3 | export default function useOrders(ids: string[]) { 4 | const [orders, setOrders] = useState([]); 5 | 6 | const fetchOrders = useCallback(async () => { 7 | const resp = await fetch("/api/brc20/orders", { 8 | method: "POST", 9 | body: JSON.stringify({ 10 | ids, 11 | }), 12 | headers: { 13 | "Content-Type": "application/json", 14 | }, 15 | }); 16 | const data = await resp.json(); 17 | setOrders(data?.data || []); 18 | }, [ids]); 19 | 20 | useEffect(() => { 21 | if (ids.length > 0) { 22 | fetchOrders(); 23 | } 24 | }, [ids, fetchOrders]); 25 | 26 | return { 27 | orders, 28 | updateOrders: fetchOrders, 29 | } 30 | } -------------------------------------------------------------------------------- /src/components/TransactionConfirm/index.tsx: -------------------------------------------------------------------------------- 1 | import Modal from "@/ui/Modal"; 2 | import React, { FC } from "react"; 3 | import Image from "next/image"; 4 | import Button from "@/ui/Button"; 5 | import { useTranslation } from "react-i18next"; 6 | import useWallet from "@/hooks/useWallet"; 7 | import toast from "react-hot-toast"; 8 | import { getPrivFromMnemonic } from "@/utils/address"; 9 | import { decrypt } from "@/utils/browser-passworder"; 10 | 11 | interface TransactionConfirmProps { 12 | visible: boolean; 13 | onClose: () => void; 14 | onConfirm: (priv: string) => void; 15 | } 16 | 17 | const TransactionConfirm: FC = ({ 18 | visible, 19 | onConfirm, 20 | onClose, 21 | }) => { 22 | const [password, setPassword] = React.useState(""); 23 | const { t } = useTranslation(); 24 | const { wallet } = useWallet(); 25 | 26 | // const [errorTips, setErrorTips] = React.useState(""); 27 | 28 | const confirmSend = async () => { 29 | if (!wallet) { 30 | return; 31 | } 32 | try { 33 | const memonic = (await decrypt(password, wallet.encryptedSeed)) as string; 34 | const priv = getPrivFromMnemonic(memonic); 35 | if (!memonic) { 36 | toast.error(t("wallet.passwordError")); 37 | return; 38 | } 39 | onConfirm(priv); 40 | onClose(); 41 | } catch (e) { 42 | console.log(t("wallet.passwordError")); 43 | toast.error(t("wallet.passwordError")); 44 | } 45 | }; 46 | 47 | return ( 48 | 49 |
50 |
51 |

52 | 56 | back 62 | 63 | {t("wallet.password")} 64 |

65 | { 70 | setPassword(e.target.value); 71 | }} 72 | /> 73 |
74 |
85 |
86 | ); 87 | }; 88 | 89 | export default TransactionConfirm; 90 | -------------------------------------------------------------------------------- /src/components/TranslationsProvider/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { I18nextProvider } from 'react-i18next'; 4 | import initTranslations from '@/locales/initI18n'; 5 | import { createInstance } from 'i18next'; 6 | import React from 'react' 7 | 8 | export default function TranslationsProvider({ 9 | children, 10 | locale, 11 | namespaces, 12 | resources 13 | }: { 14 | children: React.ReactNode, 15 | locale: string, 16 | namespaces: string[], 17 | resources: any 18 | }) { 19 | const i18n = createInstance(); 20 | 21 | initTranslations(locale, namespaces, i18n, resources); 22 | 23 | return {children}; 24 | } -------------------------------------------------------------------------------- /src/components/WalletManager/ConfirmMnemonic.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { FC, useState } from "react"; 4 | import Image from "next/image"; 5 | import { useTranslation } from "react-i18next"; 6 | import { ReactSVG } from "react-svg"; 7 | 8 | const Item: FC<{ 9 | no: number; 10 | word: string; 11 | isCorrect: boolean; 12 | onRemove: () => void; 13 | }> = ({ no, word, isCorrect, onRemove }) => { 14 | return ( 15 |
16 | 17 | {no} 18 | 19 | 24 | {word} 25 | {word && isCorrect && ( 26 | 29 | 33 | 34 | )} 35 | {word && !isCorrect && ( 36 | close 44 | )} 45 | 46 |
47 | ); 48 | }; 49 | 50 | interface Props { 51 | mnemonic: string; 52 | onConfirm: () => void; 53 | onBack: () => void 54 | } 55 | 56 | const ConfirmMnemonic: FC = ({ mnemonic, onConfirm, onBack }) => { 57 | const { t } = useTranslation(); 58 | const wordList = mnemonic.split(" "); 59 | const [selectedWords, setSelectedWords] = useState( 60 | new Array(4).fill("") as string[] 61 | ); 62 | 63 | const handleSelectWord = (word: string) => { 64 | let idx = -1; 65 | for (let i = 0; i < selectedWords.length; i++) { 66 | if (!selectedWords[i]) { 67 | idx = i; 68 | break; 69 | } 70 | } 71 | if (idx === -1) { 72 | return; 73 | } 74 | const newSelectedWords = [...selectedWords]; 75 | newSelectedWords[idx] = word; 76 | setSelectedWords(newSelectedWords); 77 | }; 78 | 79 | const handleRemoveSelectedWord = (index: number) => { 80 | const newSelectedWords = [...selectedWords]; 81 | newSelectedWords[index] = ""; 82 | setSelectedWords(newSelectedWords); 83 | }; 84 | 85 | const isPass = selectedWords.every((word, index) => { 86 | return word === wordList[index * 3 + 2]; 87 | }) 88 | 89 | return ( 90 |
91 |

Confirm back up

92 |

93 | Select words 3, 6, 9 and 12 of your mnemonic. 94 |

95 |
96 | { 101 | handleRemoveSelectedWord(0); 102 | }} 103 | /> 104 | { 109 | handleRemoveSelectedWord(1); 110 | }} 111 | /> 112 | { 117 | handleRemoveSelectedWord(2); 118 | }} 119 | /> 120 | { 125 | handleRemoveSelectedWord(3); 126 | }} 127 | /> 128 |
129 | 130 |
131 | {wordList 132 | .filter((word) => !selectedWords.includes(word)) 133 | .map((word, index) => ( 134 | { 138 | handleSelectWord(word); 139 | }} 140 | > 141 | {word} 142 | 143 | ))} 144 |
145 | 152 | 158 |
159 | ); 160 | }; 161 | 162 | export default ConfirmMnemonic; 163 | -------------------------------------------------------------------------------- /src/components/WalletManager/CreateOrRestoreWallet.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { FC, useCallback, useEffect, useState } from "react"; 4 | import { WalletCore } from "@/types/wallet"; 5 | import SelectSource from "./SelectSource"; 6 | import SetPassword from "./SetPassword"; 7 | import ConfirmMnemonic from "./ConfirmMnemonic"; 8 | import Mnemonic from "./Mnemonic"; 9 | import RestoreMnemonic from "./RestoreMnemonic"; 10 | import { generateWalletCore } from '@/utils/address' 11 | 12 | interface Props { 13 | onFinishCreateWallet: (w: WalletCore) => void; 14 | } 15 | 16 | const CreateOrRestoreWallet: FC = ({ onFinishCreateWallet }) => { 17 | /** 18 | * page value init, setPassword, mnemonic, confirmMnemonic, inputMnemonic, restoreWallet 19 | */ 20 | const [page, setPage] = useState("init"); 21 | const [source, setSource] = useState("create"); // create || restore 22 | 23 | const [tempMnemonic, setTempMnemonic] = useState(""); 24 | const [password, setPassword] = useState(""); 25 | 26 | const saveTempMnemonic = useCallback((tm: string) => { 27 | localStorage.setItem( 28 | "tempMnemonic", 29 | JSON.stringify({ tempMnemonic: tm, createdAt: Date.now() }) 30 | ); 31 | }, []); 32 | 33 | const clearTempMnemonic = useCallback(() => { 34 | localStorage.removeItem("tempMnemonic"); 35 | }, []); 36 | 37 | const handleChangeTempMnemonic = useCallback( 38 | (tm: string) => { 39 | setTempMnemonic(tm); 40 | saveTempMnemonic(tm); 41 | }, 42 | [saveTempMnemonic] 43 | ); 44 | 45 | const onCreateNewWallet = useCallback(() => { 46 | setSource("create"); 47 | setPage("setPassword"); 48 | }, []); 49 | 50 | const onRestoreWallet = useCallback(() => { 51 | setSource("restore"); 52 | setPage("setPassword"); 53 | }, []); 54 | 55 | const onCreatPasswordBack = useCallback(() => { 56 | setPage("init"); 57 | }, []); 58 | 59 | const onCreatePasswordNext = useCallback( 60 | (psw: string) => { 61 | setPassword(psw); 62 | if (source === "create") { 63 | setPage("mnemonic"); 64 | } else { 65 | setPage("inputMnemonic"); 66 | } 67 | }, 68 | [source] 69 | ); 70 | 71 | const onConfirmMnemonic = async () => { 72 | clearTempMnemonic(); 73 | const newWallet = await generateWalletCore(tempMnemonic, password) 74 | onFinishCreateWallet(newWallet); 75 | }; 76 | 77 | const onConfirmRestoreMnemonic = async (mnemonic: string) => { 78 | const newWallet = await generateWalletCore(mnemonic, password) 79 | onFinishCreateWallet(newWallet); 80 | }; 81 | 82 | if (page === "init") { 83 | return ( 84 | 88 | ); 89 | } else if (page === "setPassword") { 90 | return ( 91 | 92 | ); 93 | } else if (page === "mnemonic") { 94 | return ( 95 | { 97 | handleChangeTempMnemonic(m); 98 | setPage("confirmMnemonic"); 99 | }} 100 | /> 101 | ); 102 | } else if (page === "confirmMnemonic") { 103 | return ( 104 | { 108 | setPage("mnemonic"); 109 | }} 110 | /> 111 | ); 112 | } else if (page === "inputMnemonic") { 113 | return ( 114 | { 116 | onConfirmRestoreMnemonic(m); 117 | }} 118 | /> 119 | ); 120 | } 121 | return null; 122 | }; 123 | 124 | export default CreateOrRestoreWallet; 125 | -------------------------------------------------------------------------------- /src/components/WalletManager/Mnemonic.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useEffect } from "react"; 4 | import { useTranslation } from "react-i18next"; 5 | import { generateMnemonic } from "bip39"; 6 | import useCopy from "@/hooks/useCopy"; 7 | 8 | const MnemonicDisplay: React.FC<{ mnemonic: string }> = ({ mnemonic }) => { 9 | const wordList = mnemonic.split(" "); 10 | return ( 11 |
12 | {wordList.map((word, index) => ( 13 |
14 | 15 | {index + 1} 16 | 17 | 21 | {word} 22 | 23 |
24 | ))} 25 |
26 | ); 27 | }; 28 | 29 | interface Props { 30 | onConfirm: (mnemonic: string) => void; 31 | } 32 | 33 | const Mnemonic: React.FC = ({ onConfirm }) => { 34 | const [mnemonic, setMnemonic] = React.useState(""); 35 | const { t } = useTranslation(); 36 | 37 | useEffect(() => { 38 | const m = generateMnemonic(); 39 | setMnemonic(m); 40 | }, []); 41 | 42 | const copy = useCopy(); 43 | 44 | 45 | const handleConfirmMnemonic = () => { 46 | onConfirm(mnemonic); 47 | }; 48 | 49 | return ( 50 |
51 |

{t("wallet.secretRecoveryphrase")}

52 |

{t("wallet.mnemonicTips")}

53 | 54 | {t("wallet.mnemonicTips2")} 55 | 56 | {/*
57 | {mnemonic} 58 |
*/} 59 | 60 | 68 | 69 | 75 |
76 | ); 77 | }; 78 | 79 | export default Mnemonic; 80 | -------------------------------------------------------------------------------- /src/components/WalletManager/ReceiveModal.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Modal from "@/ui/Modal"; 4 | import React from "react"; 5 | import Image from "next/image"; 6 | import { useTranslation } from "react-i18next"; 7 | import { QRCodeCanvas } from "qrcode.react"; 8 | import { abbreviateText } from "@/utils/formater"; 9 | import Button from "@/ui/Button"; 10 | import useCopy from "@/hooks/useCopy"; 11 | 12 | interface Props { 13 | visible: boolean; 14 | onClose: () => void; 15 | address: string; 16 | } 17 | 18 | const ReceiveModal: React.FC = ({ visible, address, onClose }) => { 19 | const { t } = useTranslation(); 20 | const copy = useCopy(); 21 | return ( 22 | 23 |
24 | 25 | receive 31 | 32 |

{t("wallet.receive")}

33 |

{t("wallet.receiveTips")}

34 |
35 | 36 |
37 |
{ 40 | copy(address); 41 | }} 42 | > 43 | {abbreviateText(address, 8, 8)} 44 |
45 | 46 |
53 |
54 | ); 55 | }; 56 | 57 | export default ReceiveModal; 58 | -------------------------------------------------------------------------------- /src/components/WalletManager/RestoreMnemonic.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { FC } from "react"; 4 | import { useTranslation } from "react-i18next"; 5 | import { generateMnemonic, mnemonicToSeed, validateMnemonic } from "bip39"; 6 | 7 | interface Props { 8 | onConfirm: (mnemonic: string) => void; 9 | } 10 | 11 | const RestoreMnemonic: FC = ({ onConfirm }) => { 12 | const { t } = useTranslation(); 13 | const [mnemonic, setMnemonic] = React.useState(""); 14 | 15 | const isMnemonicValid = validateMnemonic(mnemonic); 16 | return ( 17 |
18 |

{t("wallet.secretRecoveryphrase")}

19 |

{t("wallet.mnemonicTips")}

20 |