├── .gitignore ├── LICENSE ├── README.md ├── babel.config.json ├── components ├── AuctionContainer.js ├── AuctionCountdown.js ├── BitBoxWallet.js ├── CollectionThumbnail.js ├── Footer.js ├── Header.js ├── Html5QrcodeScannerPlugin.js ├── InscriptionsDetails.js ├── MultistepCheckout │ ├── BRC.js │ ├── ContentSelector.js │ ├── DomainInput.js │ ├── FeeRange.js │ ├── FileUpload.js │ ├── MultiStep.js │ ├── NewsInput.js │ ├── OP_RETURN.js │ ├── OnchainInput.js │ ├── Overview.js │ ├── Price.js │ ├── Rune.js │ ├── TAP.js │ ├── TextInput.js │ └── WalletSelect.js ├── OrdinalCard.js ├── OrdinalExplorer │ ├── BlockCloud.js │ ├── BlockStats.js │ ├── OrdinalGrid.js │ └── TagCloud.js ├── OrdinalThumbnail.js ├── TestnetSwitch.js ├── UtxoImage.js ├── UtxoInfo.js ├── WalletConfig │ ├── connectLedger.js │ ├── constance.js │ ├── hiroWalletFunctions.js │ ├── ordimintWalletFunctions.js │ ├── unisatWalletFunctions.js │ ├── utils.js │ └── xverseWalletFunctions.js └── modals │ ├── AlterModal.js │ ├── AuctionModal.js │ ├── AuctionSteps │ ├── AlbyWalletAuction.js │ ├── CreateOffer.js │ ├── InscriptionsDetailsAuction.js │ ├── LedgerWalletAuction.js │ ├── OrdimintWalletAuction.js │ ├── PayOffer.js │ ├── SelectOrdinal.js │ ├── SelectWallet.js │ ├── UtxoImageAuction.js │ └── UtxoInfoAuction.js │ ├── BeginSendModal.js │ ├── ConfirmationModal.js │ ├── GenerateWalletModal.js │ ├── InvoiceModal.js │ ├── OrdimintWalletInfo.js │ ├── ReceiveAddressModal.js │ ├── RestoreWalletModal.js │ ├── SelectFeeRateModal.js │ ├── SelectWalletModal.js │ ├── SentModal.js │ ├── SingleOrdinalModal.js │ ├── UtxoModal.js │ └── WalletConnectModal.js ├── contexts └── TestnetContext.js ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── 404.js ├── _app.js ├── _document.js ├── _error.js ├── check-order.js ├── explorer │ ├── block │ │ └── [slug].js │ ├── index.js │ └── inscription │ │ └── [slug].js ├── faq.js ├── index.js ├── market │ └── index.js ├── ordinal-collections.js ├── ordinal-collections │ └── [slug].js ├── partners.js ├── search.js └── wallet │ ├── alby.js │ ├── index.js │ ├── leather.js │ ├── ledger.js │ ├── ordimint.js │ ├── unisat.js │ └── xverse.js ├── public ├── Ordimint-Twitter-card.png ├── OrdimintSVGLogo-black.svg ├── OrdimintSVGLogo.svg ├── apple-touch-icon.png ├── data │ └── collections.js ├── favicon.ico ├── font.css ├── fonts │ └── Jost-Regular.ttf ├── functions │ ├── auctionFunctions.js │ └── ordinalFunctions.js ├── index.css ├── logo-dark.jpeg ├── logo.png ├── manifest.json ├── media │ ├── HiroWalletLogo.jpg │ ├── LeatherWalletLogo.svg │ ├── OrdimintSVGLogo-black.svg │ ├── OrdimintSVGLogo.svg │ ├── alby-logo.svg │ ├── alby_icon_yellow.png │ ├── alby_icon_yellow.svg │ ├── bitbox-logo.png │ ├── dorian-nakamoto.jpg │ ├── glow1.svg │ ├── glow2.svg │ ├── green-check.gif │ ├── ledger-logo-small.svg │ ├── ledger-logo.svg │ ├── logo-dark.jpeg │ ├── logo.png │ ├── nos-ft-logo.png │ ├── ordimint-coin-white.png │ ├── ordimint-coin.png │ ├── text-placeholder.png │ ├── unisat-logo.svg │ ├── xverse-logo.jpg │ └── xverse-logo.png ├── noThumbnail.html ├── sitemap.xml └── theme.css └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | /dist 14 | 15 | public/robots.txt 16 | 17 | 18 | #collections 19 | src/data/collections.js 20 | # misc 21 | .DS_Store 22 | .env.local 23 | .env.development.local 24 | .env.test.local 25 | .env.production.local 26 | .env 27 | .next 28 | 29 | npm-debug.log* 30 | yarn-debug.log* 31 | yarn-error.log* 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 ordimint 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # web-app 2 | A web app to deal with Ordinals 3 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | [ 5 | "@babel/preset-react", 6 | { 7 | "runtime": "automatic" 8 | } 9 | ] 10 | ], 11 | "env": { 12 | "production": { 13 | "plugins": [ 14 | [ 15 | "inline-dotenv", 16 | { 17 | "path": ".env" 18 | } 19 | ] 20 | ] 21 | }, 22 | "development": { 23 | "plugins": [ 24 | [ 25 | "inline-dotenv", 26 | { 27 | "path": ".env.local" 28 | } 29 | ] 30 | ] 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /components/AuctionContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Container } from 'react-bootstrap' 3 | 4 | const AuctionContainer = () => { 5 | return ( 6 | 7 |

Available offers

8 | 9 |
10 | ) 11 | } 12 | 13 | export default AuctionContainer 14 | -------------------------------------------------------------------------------- /components/AuctionCountdown.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | 3 | const AuctionCountdown = () => { 4 | const [timeLeft, setTimeLeft] = useState(calculateTimeLeft()); 5 | 6 | useEffect(() => { 7 | setTimeLeft(calculateTimeLeft()); 8 | 9 | const timer = setInterval(() => { 10 | setTimeLeft(calculateTimeLeft()); 11 | }, 1000); 12 | 13 | return () => clearInterval(timer); 14 | }, []); 15 | 16 | function calculateTimeLeft() { 17 | let now = new Date(); 18 | let then = new Date(); 19 | 20 | if (now.getHours() < 12) { 21 | then.setHours(12, 0, 0, 0); 22 | } else { 23 | then.setHours(24, 0, 0, 0); 24 | } 25 | 26 | let difference = then - now; 27 | 28 | let hoursLeft = Math.floor(difference / (1000 * 60 * 60)); 29 | let minutesLeft = Math.floor((difference / (1000 * 60)) % 60); 30 | let secondsLeft = Math.floor((difference / 1000) % 60); 31 | 32 | return { 33 | hours: hoursLeft.toString().padStart(2, '0'), 34 | minutes: minutesLeft.toString().padStart(2, '0'), 35 | seconds: secondsLeft.toString().padStart(2, '0'), 36 | }; 37 | } 38 | 39 | return ( 40 |
41 |
Next price update for auctions in: {timeLeft.hours}:{timeLeft.minutes}:{timeLeft.seconds}
42 |
43 | Updates always at 12:00 and 24:00 (UTC) 44 |
45 |
46 | ); 47 | }; 48 | 49 | export default AuctionCountdown; 50 | -------------------------------------------------------------------------------- /components/CollectionThumbnail.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Figure } from 'react-bootstrap' 3 | import { useRouter } from 'next/router' 4 | 5 | const isValidUrl = (string) => { 6 | try { 7 | new URL(string); 8 | } catch (_) { 9 | return false; 10 | } 11 | return true; 12 | } 13 | 14 | const CollectionThumbnail = (props) => { 15 | const router = useRouter(); 16 | const icon = props.collection.inscription_icon; 17 | const url = icon ? `https://explorer.ordimint.com/preview/${icon}` : ''; 18 | const validUrl = isValidUrl(url); 19 | const placeholderUrl = '/noThumbnail.html'; // replace with your placeholder image URL 20 | 21 | const handleClick = () => { 22 | router.push(`/collections/${props.collection.slug}`); 23 | } 24 | 25 | return ( 26 |
27 | 31 |
32 | 33 |

34 | {props.collection.name} 35 |

36 |
37 |
38 | ); 39 | } 40 | 41 | export default CollectionThumbnail; -------------------------------------------------------------------------------- /components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { FaTwitterSquare, FaGithub, FaYoutube, FaTelegramPlane } from "react-icons/fa"; 3 | import Link from "next/link"; 4 | import Image from 'next/image'; 5 | 6 | 7 | const Footer = () => { 8 | return ( 9 |
10 |
11 |
12 | 13 | 14 |
15 | 16 | 30 |
31 |
32 |
33 | 34 | 35 |
36 |
37 |
Ordimint. ©2023. All rights reserved
38 |
39 |
40 | 41 |
45 |
49 | 50 |
51 | 52 |
53 | 54 |
55 |
56 |
57 | ); 58 | }; 59 | 60 | export default Footer; -------------------------------------------------------------------------------- /components/Header.js: -------------------------------------------------------------------------------- 1 | import { Navbar, Nav, Badge, Offcanvas, Container } from "react-bootstrap"; 2 | import { useRouter } from 'next/router'; 3 | import Link from "next/link"; 4 | import Image from 'next/image'; 5 | 6 | 7 | const Header = (props) => { 8 | 9 | const router = useRouter(); 10 | 11 | const isActive = (href) => { 12 | if (href === "/") { 13 | return router.pathname === href ? "active_nav" : "inactive_nav"; 14 | } 15 | return router.pathname.startsWith(href) ? "active_nav" : 'inactive_nav'; 16 | }; 17 | 18 | return ( 19 |
20 | 21 | 30 |
35 |
36 | 37 |
38 | 39 |
40 | 41 |
42 |
43 | 44 | 45 | 46 | Ordimint Brand Logo 54 | 55 | 56 | 57 | 58 | 59 | 60 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 76 | 77 | 78 | 133 | 134 | 135 |
136 | {/* 137 | 142 | */} 143 |
144 |
145 | ); 146 | }; 147 | 148 | export default Header; 149 | -------------------------------------------------------------------------------- /components/Html5QrcodeScannerPlugin.js: -------------------------------------------------------------------------------- 1 | // file = Html5QrcodePlugin.jsx 2 | import { Html5QrcodeScanner } from 'html5-qrcode'; 3 | import { useEffect, useState } from 'react'; 4 | import { FaCamera, FaTimes } from 'react-icons/fa'; 5 | 6 | 7 | const qrcodeRegionId = "html5qr-code-full-region"; 8 | 9 | // Creates the configuration object for Html5QrcodeScanner. 10 | const createConfig = (props) => { 11 | let config = {}; 12 | if (props.fps) { 13 | config.fps = props.fps; 14 | } 15 | if (props.qrbox) { 16 | config.qrbox = props.qrbox; 17 | } 18 | if (props.aspectRatio) { 19 | config.aspectRatio = props.aspectRatio; 20 | } 21 | if (props.disableFlip !== undefined) { 22 | config.disableFlip = props.disableFlip; 23 | } 24 | return config; 25 | }; 26 | 27 | const Html5QrcodePlugin = (props) => { 28 | const [showScanner, setShowScanner] = useState(false); 29 | 30 | const handleCameraClick = () => { 31 | setShowScanner(prev => !prev); 32 | } 33 | 34 | 35 | useEffect(() => { 36 | if (!showScanner) return; 37 | // when component mounts 38 | const config = createConfig(props); 39 | const verbose = props.verbose === true; 40 | // Suceess callback is required. 41 | if (!(props.qrCodeSuccessCallback)) { 42 | throw "qrCodeSuccessCallback is required callback."; 43 | } 44 | const html5QrcodeScanner = new Html5QrcodeScanner(qrcodeRegionId, config, verbose); 45 | html5QrcodeScanner.render(props.qrCodeSuccessCallback, props.qrCodeErrorCallback); 46 | 47 | // cleanup function when component will unmount 48 | return () => { 49 | html5QrcodeScanner.clear().catch(error => { 50 | console.error("Failed to clear html5QrcodeScanner. ", error); 51 | }); 52 | }; 53 | }, [showScanner]); 54 | 55 | return ( 56 |
57 | {showScanner ? 58 | : 59 | 60 | } 61 | {showScanner &&
} 62 |
63 | ); 64 | 65 | }; 66 | 67 | export default Html5QrcodePlugin; -------------------------------------------------------------------------------- /components/InscriptionsDetails.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useContext, useState } from 'react'; 2 | import { TailSpin } from 'react-loading-icons'; 3 | import { TestnetContext } from '../contexts/TestnetContext'; 4 | import { Button } from 'react-bootstrap'; 5 | 6 | const InscriptionsDetails = (props) => { 7 | const { testnet } = useContext(TestnetContext); 8 | const [inscriptionData, setInscriptionData] = useState(null); 9 | const explorerURL = testnet ? process.env.REACT_APP_TESTNET_URL : process.env.REACT_APP_MAINNET_URL; 10 | useEffect(() => { 11 | 12 | getInscriptionData(props.utxo); 13 | 14 | }, [testnet, props.utxo]); 15 | 16 | const getInscriptionDetails = async (inscriptionId) => { 17 | 18 | try { 19 | const response = await fetch(`${explorerURL}/inscription/${inscriptionId}`, { 20 | headers: { 21 | 'Accept': 'application/json' 22 | } 23 | }); 24 | if (!response.ok) { 25 | throw new Error(`HTTP error! Status: ${response.status}`); 26 | } 27 | if (response.headers.get('content-type').includes('application/json')) { 28 | const responseJSON = await response.json(); 29 | 30 | return { 31 | inscription_number: responseJSON.inscription_number, 32 | value: responseJSON.output_value, 33 | timestamp: responseJSON.timestamp, 34 | inscription_id: responseJSON.inscription_id 35 | }; 36 | } else { 37 | console.error('The response is not in JSON format'); 38 | return null; 39 | } 40 | } catch (error) { 41 | console.error(error); 42 | return null; 43 | } 44 | }; 45 | 46 | async function getInscriptionData(utxo) { 47 | try { 48 | const explorerURL = testnet ? process.env.REACT_APP_TESTNET_URL : process.env.REACT_APP_MAINNET_URL; 49 | if (!explorerURL) { 50 | throw new Error('Explorer URL is not defined'); 51 | } 52 | const response = await fetch(`${explorerURL}/output/${utxo.txid}:${utxo.vout}`, { 53 | headers: { 54 | 'Accept': 'application/json' 55 | } 56 | }); 57 | if (response.ok && response.headers.get('content-type').includes('application/json')) { 58 | const outputData = await response.json(); 59 | 60 | // Check if inscriptions array is present and has at least one inscription 61 | if (Array.isArray(outputData.inscriptions) && outputData.inscriptions.length > 0) { 62 | const inscriptionId = outputData.inscriptions[0]; // Take the first inscription 63 | const inscriptionDetails = await getInscriptionDetails(inscriptionId); 64 | 65 | if (inscriptionDetails) { 66 | setInscriptionData(inscriptionDetails); 67 | } else { 68 | console.error('Failed to fetch inscription details'); 69 | } 70 | } else { 71 | console.error('No inscriptions found'); 72 | } 73 | } else { 74 | console.error('Failed to fetch data'); 75 | } 76 | } catch (e) { 77 | console.error(e); 78 | } 79 | } 80 | 81 | return ( 82 |
83 | 84 |
85 | {inscriptionData ? ( 86 | <> 87 |
#{inscriptionData.inscription_number.toLocaleString('en-US')}
88 |
{inscriptionData.value} Sats
89 |
90 | {new Date(inscriptionData.timestamp * 1000).toLocaleDateString()}
91 | {new Date(inscriptionData.timestamp * 1000).toLocaleTimeString()} 92 |
93 | 94 | 95 | 96 | 97 | ) : ( 98 |

99 |
100 | 101 |
102 | Loading... 103 |

104 | )} 105 |
106 | 107 |
108 | ); 109 | }; 110 | 111 | export default InscriptionsDetails; 112 | -------------------------------------------------------------------------------- /components/MultistepCheckout/BRC.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useState, useEffect } from 'react' 3 | import { Form, InputGroup, Button } from 'react-bootstrap' 4 | 5 | const BRC = (props) => { 6 | 7 | const transferFields = ( 8 | <> 9 | 10 | 11 | Amount 12 | 13 | { 19 | props.setTransferAmount(e.target.value); 20 | }} 21 | /> 22 | 23 | 24 | ); 25 | 26 | 27 | return ( 28 |
29 |
30 |
Deploy, mint or transfer BRC-20 token.
31 |
32 |
33 | 34 |
35 | 36 | { props.onChange(e.target.value) }} 37 | checked={props.brcRadioButton === "deploy"} /> 38 | 41 | 42 | 43 | { props.onChange(e.target.value) }} 44 | checked={props.brcRadioButton === "mint"} /> 45 | 48 | 49 | { props.onChange(e.target.value) }} 50 | checked={props.brcRadioButton === "transfer"} /> 51 | 54 | 55 |
56 | 57 |
58 | 59 | 60 | Ticker 61 | { 68 | props.setFileSize(((e.target.value.length))); 69 | props.setTokenTicker(e.target.value); 70 | } 71 | } 72 | /> 73 | 74 | {props.brcRadioButton === "deploy" ? ( 75 | <> 76 | 77 | Total Supply 78 | { 85 | props.setTokenSupply(e.target.value); 86 | } 87 | } 88 | /> 89 | 90 | 91 | Limit Per Mint 92 | { 99 | props.setMintLimit(e.target.value); 100 | } 101 | } 102 | /> 103 | 104 | ) 105 | : props.brcRadioButton === 'transfer' ? ( 106 | transferFields 107 | ) : ( 108 | <> 109 | 110 | Amount 111 | { 118 | props.setMintAmount(e.target.value); 119 | } 120 | } 121 | /> 122 | 123 | 124 | ) 125 | 126 | 127 | } 128 |

Read more about BRC-20 tokens here

129 |
130 | ) 131 | } 132 | 133 | export default BRC 134 | -------------------------------------------------------------------------------- /components/MultistepCheckout/DomainInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { InputGroup, Form, Button } from 'react-bootstrap' 3 | import { useEffect, useState } from 'react' 4 | const DomainInput = (props) => { 5 | 6 | 7 | 8 | return ( 9 | 10 |
11 |
12 | 13 | 14 | { 22 | props.setDomainInput(e.target.value); 23 | props.setFileSize(((e.target.value.length))); 24 | // props.checkDomain(e.target.value) 25 | } 26 | 27 | } 28 | /> 29 | .sats 30 | 31 | 32 |

Learn more about Sats Names

33 |
34 |
35 | ) 36 | } 37 | 38 | export default DomainInput 39 | -------------------------------------------------------------------------------- /components/MultistepCheckout/FeeRange.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | import React, { useEffect, useState } from 'react'; 4 | import { ToggleButtonGroup, ToggleButton } from 'react-bootstrap'; 5 | import Price from './Price'; 6 | 7 | const getFeesRecommended = async () => { 8 | const response = await fetch('https://mempool.space/api/v1/fees/recommended'); 9 | const data = await response.json(); 10 | return data; 11 | }; 12 | 13 | const FeeRange = (props) => { 14 | const [minFee, setMinFee] = useState(0); 15 | const [mediumFee, setMediumFee] = useState(20); 16 | const [maxFee, setMaxFee] = useState(0); 17 | const [selectedFee, setSelectedFee] = useState(null); 18 | 19 | useEffect(() => { 20 | const fetchFees = async () => { 21 | const result = await getFeesRecommended(); 22 | setMinFee(result.economyFee); 23 | setMediumFee(result.halfHourFee); 24 | setMaxFee(result.fastestFee); 25 | setSelectedFee(result.halfHourFee); 26 | props.setFee({ target: { value: result.halfHourFee } }); 27 | }; 28 | 29 | fetchFees(); 30 | }, []); 31 | 32 | const handleFeeChange = (val) => { 33 | setSelectedFee(val); 34 | props.setFee({ target: { value: val } }); 35 | }; 36 | 37 | return ( 38 | 39 |
40 |
How urgent is your order?
41 |
42 | 43 | handleFeeChange(minFee)} title="slow"> 44 |
Slow
45 |
> 1 day
{minFee} sats/vByte

46 |
47 | handleFeeChange(mediumFee)} 51 | title="normal" 52 | 53 | > 54 |
Normal
55 |
4 hours
{mediumFee} sats/vByte

56 |
57 | handleFeeChange(maxFee)} 60 | title="fast" 61 | 62 | > 63 |
Fast
64 |
1 hour
{maxFee} sats/vByte

65 |
66 |
67 | 68 |
69 | 70 |
71 | ); 72 | }; 73 | 74 | export default FeeRange; 75 | -------------------------------------------------------------------------------- /components/MultistepCheckout/FileUpload.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback, useContext } from 'react'; 2 | import { Form, Image } from 'react-bootstrap'; 3 | import { useDropzone } from 'react-dropzone'; 4 | import imageCompression from 'browser-image-compression'; 5 | import { AiOutlineCloudUpload } from 'react-icons/ai'; 6 | 7 | 8 | const acceptedFileTypes = { 9 | 'image/*': ['.apng', '.gif', '.jpeg', '.jpg', '.png', '.svg', '.webp', '.avif'], 10 | 'text/*': ['.html', '.txt', '.css', '.js', '.md'], 11 | 'audio/*': ['.flac', '.mpeg', '.wav'], 12 | 'video/*': ['.webm', '.mp4'], 13 | 'application/pdf': ['.pdf'], 14 | 'application/json': ['.json'], 15 | 'application/pgp-signature': ['.pgp'], 16 | 'application/yaml': ['.yaml'], 17 | 'model/gltf-binary': ['.gltf'], 18 | 'model/stl': ['.stl'] 19 | }; 20 | 21 | 22 | const FileUpload = (props) => { 23 | 24 | const [compressImage, setCompressImage] = useState(true); 25 | const [originalFile, setOriginalFile] = useState(null); 26 | const [compressedImageURL, setCompressedImageURL] = useState(null); 27 | 28 | 29 | const compressAndSetImage = async (file) => { 30 | try { 31 | 32 | const compressedFile = await imageCompression(file, { 33 | maxSizeMB: 0.3, 34 | maxWidthOrHeight: 1280, 35 | quality: 0.3, 36 | resize: true, 37 | initialQuality: 0.5, 38 | alwaysKeepResolution: true, 39 | useWebWorker: true, 40 | }); 41 | props.setFileSize(compressedFile.size); 42 | setCompressedImageURL(URL.createObjectURL(compressedFile)); 43 | props.setFile(URL.createObjectURL(compressedFile)); 44 | props.setFileName(compressedFile.name); 45 | 46 | } catch (error) { 47 | console.error('Image compression failed:', error); 48 | } 49 | }; 50 | 51 | 52 | const onDrop = useCallback(async (acceptedFiles) => { 53 | const file = acceptedFiles[0]; 54 | const fileExtension = file.name.split('.').pop().toLowerCase(); 55 | 56 | // if (!acceptedFileTypes.includes(fileExtension)) { 57 | // alert('Unsupported file type. Please upload a supported file.'); 58 | // return; 59 | // } 60 | 61 | 62 | const sizeLimit = 700000; 63 | 64 | if (acceptedFiles[0].size > sizeLimit) { 65 | props.fileTooBig(); 66 | } else { 67 | const fileType = acceptedFiles[0].name.split('.').pop(); 68 | props.setFileType(fileType); 69 | setOriginalFile(acceptedFiles[0]); 70 | 71 | if (compressImage && isImage(fileType)) { 72 | await compressAndSetImage(acceptedFiles[0]); 73 | } else { 74 | props.setFileSize(acceptedFiles[0].size); 75 | props.setFile(URL.createObjectURL(acceptedFiles[0])); 76 | props.setFileName(acceptedFiles[0].name); 77 | } 78 | } 79 | }, [compressImage]); 80 | 81 | 82 | const { getRootProps, getInputProps, isDragActive } = useDropzone({ 83 | onDrop, 84 | accept: acceptedFileTypes, 85 | maxFiles: 1, 86 | }); 87 | 88 | 89 | const handleCompressionChange = async (e) => { 90 | const isChecked = e.target.checked; 91 | setCompressImage(isChecked); 92 | 93 | if (props.file && isImage(props.fileType)) { 94 | if (isChecked) { 95 | await compressAndSetImage(await fetch(props.file).then((res) => res.blob())); 96 | } else if (originalFile) { 97 | // Revert to the original file 98 | props.setFileSize(originalFile.size); 99 | props.setFile(URL.createObjectURL(originalFile)); 100 | props.setFileName(originalFile.name); 101 | } 102 | } 103 | }; 104 | 105 | 106 | const isImage = (fileType) => { 107 | return ['apng', 'gif', 'jpg', 'jpeg', 'png', 'webp'].includes(fileType); 108 | }; 109 | 110 | return ( 111 | <> 112 |
113 | {props.file && ( 114 |
115 | {isImage(props.fileType) ? ( 116 | Uploaded file preview 123 | ) : ( 124 |
{props.fileName}
125 | )} 126 |
127 | )} 128 |
129 | 130 | {isDragActive ? ( 131 |

Drop the files here...

132 | ) : ( 133 | <> 134 | 135 |

136 | 137 | {' '} 138 | Drag and drop a file here, or click to
select a file you want to inscribe.

139 | 140 | )} 141 |
142 | {isImage(props.fileType) && ( 143 | 144 |
145 | 150 | 153 |
154 | {/* */} 161 |
162 | )} 163 |
164 | 165 | ); 166 | }; 167 | 168 | export default FileUpload; 169 | -------------------------------------------------------------------------------- /components/MultistepCheckout/MultiStep.js: -------------------------------------------------------------------------------- 1 | import { set } from 'mongoose'; 2 | import React, { useState } from 'react'; 3 | import { Button, ProgressBar } from 'react-bootstrap'; 4 | 5 | const MultiStep = ({ steps, handleSubmit, validateContent, validateOnchainAddress }) => { 6 | const [currentStep, setCurrentStep] = useState(1); 7 | 8 | const calculateProgress = () => { 9 | return (currentStep / steps.length) * 100; 10 | }; 11 | 12 | 13 | const nextStep = () => { 14 | if (currentStep < steps.length) { 15 | 16 | if (validations[currentStep - 1]()) { 17 | 18 | setCurrentStep(prevStep => prevStep + 1); 19 | } 20 | } 21 | }; 22 | 23 | const prevStep = () => { 24 | if (currentStep > 1) { 25 | setCurrentStep(prevStep => prevStep - 1); 26 | } 27 | }; 28 | 29 | const validateStep1 = () => { 30 | return validateContent(); 31 | }; 32 | 33 | const validateStep2 = () => { 34 | return validateOnchainAddress(); 35 | }; 36 | 37 | const validateStep3 = () => { 38 | 39 | return true; 40 | }; 41 | 42 | const validateStep4 = () => { 43 | 44 | return true; 45 | }; 46 | 47 | const validations = [validateStep1, validateStep2, validateStep3, validateStep4]; 48 | 49 | const stepHeadlines = [ 50 | "What do you want to inscribe?", 51 | "Choose receiver address", 52 | "Choose fee", 53 | "Summary" 54 | ]; 55 | 56 | 57 | 58 | return ( 59 |
60 |
61 |

{stepHeadlines[currentStep - 1]}

62 |
63 |
64 | 65 |
66 | 67 |
68 | {steps[currentStep - 1]} 69 |
70 |
71 | {currentStep > 1 && 72 | } 78 | {currentStep < steps.length ? ( 79 | 85 | ) : ( 86 | 93 | )} 94 |
95 |
96 | ); 97 | }; 98 | 99 | export default MultiStep; 100 | -------------------------------------------------------------------------------- /components/MultistepCheckout/NewsInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useState, useEffect } from 'react' 3 | import { Form, InputGroup, Container } from 'react-bootstrap' 4 | 5 | const NewsInput = (props) => { 6 | return ( 7 | 8 |
9 | 10 | Title 11 | { 17 | props.setNewsTitle(e.target.value); 18 | } 19 | } 20 | /> 21 | 22 | 23 | URL 24 | { 30 | props.setNewsUrl(e.target.value); 31 | } 32 | } 33 | /> 34 | 35 | 36 | Author 37 | { 43 | props.setNewsAuthor(e.target.value); 44 | } 45 | } 46 | /> 47 | 48 | 49 | Body 50 | { 55 | props.setNewsText(e.target.value); 56 | props.setFileSize(((e.target.value.length))); 57 | } 58 | 59 | } 60 | autoFocus={true} 61 | id="exampleFormControlTextarea1" 62 | rows="12" /> 63 | 64 |

Read more about Ordinals News Standard

65 |
66 | 67 | 68 | 69 | ) 70 | } 71 | 72 | export default NewsInput 73 | -------------------------------------------------------------------------------- /components/MultistepCheckout/OP_RETURN.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { InputGroup, Form, Button } from 'react-bootstrap' 3 | 4 | 5 | 6 | 7 | const OP_RETURN = (props) => { 8 | let prevValue = ""; 9 | return ( 10 | 11 |
12 | 13 | { 18 | 19 | const byteSize = new Blob([e.target.value]).size; 20 | if (byteSize > 80) { 21 | props.OP_RETURNTooBig(); 22 | props.setOpReturnInput(prevValue); // Reset to previous value if byte size is too big 23 | } else { 24 | prevValue = e.target.value; // Update the previous value if byte size is within limit 25 | props.setOpReturnInput(e.target.value); 26 | } 27 | } 28 | } 29 | /> 30 | 31 | 32 |

Learn more about OP_RETURN.

33 |
34 | 35 | ) 36 | } 37 | 38 | export default OP_RETURN 39 | -------------------------------------------------------------------------------- /components/MultistepCheckout/OnchainInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { InputGroup, Form } from 'react-bootstrap' 3 | 4 | const OnchainInput = (props) => { 5 | return ( 6 | 7 |
8 | 9 | {/* Receiver address */} 10 | props.setOnChainAddress(e.target.value)} 17 | 18 | /> 19 | 20 |
21 | ) 22 | } 23 | 24 | export default OnchainInput 25 | -------------------------------------------------------------------------------- /components/MultistepCheckout/Price.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useState, useEffect } from 'react'; 3 | import axios from "axios"; 4 | import { BsInfoCircle } from "react-icons/bs"; 5 | import { Tooltip, OverlayTrigger } from 'react-bootstrap'; 6 | 7 | const Price = (props) => { 8 | 9 | const [priceUSDinSats, setPrice] = useState(0); 10 | 11 | useEffect(() => { 12 | async function getPrice() { 13 | return axios({ 14 | method: "get", 15 | url: process.env.REACT_APP_PRICE_API, 16 | }).then(function (response) { 17 | return 100_000_000 / response.data.USD.buy; 18 | }); 19 | } 20 | getPrice().then((result) => { 21 | setPrice(result); 22 | }); 23 | }, []); 24 | 25 | 26 | 27 | 28 | return ( 29 | 30 |
31 |
32 |
33 | 34 |
{Math.round(props.price)} Sats {" "} 35 | 36 | 37 |
38 | 39 |
{(props.price / priceUSDinSats).toFixed(2)}$
40 | 41 |
42 |
43 |
44 | ) 45 | } 46 | 47 | export default Price 48 | -------------------------------------------------------------------------------- /components/MultistepCheckout/Rune.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useState, useEffect } from 'react' 3 | import { Form, InputGroup, Button } from 'react-bootstrap' 4 | 5 | const Rune = (props) => { 6 | 7 | const transferFields = ( 8 | <> 9 | 10 | 11 | Amount 12 | 13 | { 19 | props.setTransferAmount(e.target.value); 20 | }} 21 | /> 22 | 23 | 24 | ); 25 | 26 | 27 | return ( 28 |
29 |
30 |
Deploy Rune
31 |
32 |
33 | 34 |
35 | 36 | { props.onChange(e.target.value) }} 37 | checked={props.brcRadioButton === "deploy"} /> 38 | 41 | 42 | 43 | {/* { props.onChange(e.target.value) }} 44 | checked={props.brcRadioButton === "mint"} /> 45 | 48 | 49 | { props.onChange(e.target.value) }} 50 | checked={props.brcRadioButton === "transfer"} /> 51 | */} 54 | 55 |
56 | 57 |
58 | 59 | 60 | Ticker 61 | { 68 | props.setFileSize(((e.target.value.length))); 69 | props.setTokenTicker(e.target.value); 70 | } 71 | } 72 | /> 73 | 74 | {props.brcRadioButton === "deploy" ? ( 75 | <> 76 | 77 | Total Supply 78 | { 85 | props.setTokenSupply(e.target.value); 86 | } 87 | } 88 | /> 89 | 90 | {/* 91 | Limit Per Mint 92 | { 99 | props.setMintLimit(e.target.value); 100 | } 101 | } 102 | /> 103 | */} 104 | ) 105 | : props.brcRadioButton === 'transfer' ? ( 106 | transferFields 107 | ) : ( 108 | <> 109 | 110 | Amount 111 | { 118 | props.setMintAmount(e.target.value); 119 | } 120 | } 121 | /> 122 | 123 | 124 | ) 125 | 126 | 127 | } 128 | {/*

Read more about BRC-20 tokens here

*/} 129 |
130 | ) 131 | } 132 | 133 | export default Rune 134 | -------------------------------------------------------------------------------- /components/MultistepCheckout/TextInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const TextInput = (props) => { 4 | return ( 5 |
6 |