├── .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 |
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 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
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 | Details
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 |
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 |
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 |
151 | Compress image
152 |
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 |
66 |
67 |
68 | {steps[currentStep - 1]}
69 |
70 |
71 | {currentStep > 1 &&
72 | Back }
78 | {currentStep < steps.length ? (
79 | Next
85 | ) : (
86 | Submit & Pay
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 |
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 |
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 |
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 |
21 | )
22 | }
23 |
24 | export default TextInput
25 |
--------------------------------------------------------------------------------
/components/OrdinalExplorer/BlockCloud.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react';
2 | import { Stack } from 'react-bootstrap';
3 | import { useRouter } from 'next/router';
4 | import { Badge } from 'react-bootstrap';
5 |
6 |
7 | const BlockCloud = ({ selectedBlock, blockHeight }) => {
8 | const router = useRouter()
9 | const blockCloudRef = useRef(null); // Add this line
10 | // Ensure that selectedBlock is within the range of blocks
11 | const blocks = Array.from({ length: blockHeight - 747899 + 10 + 1 }, (_, i) => blockHeight + 10 - i);
12 |
13 | useEffect(() => {
14 | const blockCloud = blockCloudRef.current;
15 | const activeBadge = blockCloud.querySelector('.active-badge');
16 |
17 | if (activeBadge) {
18 | const blockCloudMiddle = blockCloud.clientWidth / 2;
19 | const activeBadgeMiddle = activeBadge.offsetLeft + activeBadge.clientWidth / 2;
20 | const scrollLeft = activeBadgeMiddle - blockCloudMiddle;
21 |
22 | blockCloud.scrollLeft = scrollLeft;
23 | }
24 | }, [selectedBlock]);
25 |
26 | return (
27 |
53 | )
54 | };
55 |
56 | export default BlockCloud;
--------------------------------------------------------------------------------
/components/OrdinalExplorer/BlockStats.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export async function getServerSideProps(context) {
4 | let blockStats = null;
5 | const url = `${process.env.NEXT_PUBLIC_API_URL}`;
6 |
7 | try {
8 | const response = await fetch(url, {
9 | headers: {
10 | 'Accept': 'application/json'
11 | }
12 | });
13 | blockStats = await response.json();
14 | } catch (error) {
15 | console.error('Error:', error);
16 | }
17 |
18 | return { props: { blockStats } }
19 | }
20 |
21 |
22 |
23 | const BlockStats = ({ blockStats }) => {
24 | return (
25 |
26 |
27 |
28 | )
29 | }
30 |
31 | export default BlockStats
32 |
--------------------------------------------------------------------------------
/components/OrdinalExplorer/OrdinalGrid.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Row, Col, Spinner } from 'react-bootstrap';
3 | // import OrdinalCard from '../OrdinalCard';
4 | import { Suspense, lazy } from 'react';
5 | const OrdinalCard = lazy(() => import('../OrdinalCard'));
6 |
7 |
8 | const OrdinalGrid = ({ ordinalsData }) => {
9 |
10 | return (
11 |
12 | {
13 | Array.isArray(ordinalsData) && ordinalsData.map((ordinalData, index) => {
14 | return (
15 |
16 |
17 | }>
18 |
25 |
26 |
27 | );
28 | })
29 | }
30 |
31 | );
32 | }
33 |
34 | export default OrdinalGrid
35 |
36 |
--------------------------------------------------------------------------------
/components/OrdinalExplorer/TagCloud.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import { Badge, Stack } from 'react-bootstrap'
3 | import { useRouter } from 'next/router'
4 |
5 | const TagCloud = ({ selectedTags, setSelectedTags }) => {
6 | const [tags, setTags] = useState(['All', 'Images', 'Videos', 'GIFs', 'Text', 'Games', 'Audio', 'SVG', 'HTML'])
7 | const router = useRouter()
8 |
9 | useEffect(() => {
10 | setSelectedTags(['All']) // Set 'All' as the default selected tag
11 | }, [])
12 | const handleTagClick = (tag) => {
13 | setSelectedTags([tag]) // Only the clicked tag is active
14 | if (tag !== 'All') {
15 | router.push({
16 | pathname: router.pathname,
17 | query: { 'content-type': tag.toLowerCase() },
18 | }) // Update the URL with the selected tag
19 | } else {
20 | router.push(router.pathname) // If 'All' is selected, remove the query string
21 | }
22 | }
23 |
24 | return (
25 |
26 |
27 | {tags.map((tag, index) => (
28 | handleTagClick(tag)}
32 | pill
33 | bg={selectedTags.includes(tag) ? "light" : "secondary"} // If the tag is active, use a filled background
34 | text={selectedTags.includes(tag) ? "dark" : "light"} // If the tag is active, use light text
35 | >
36 | {tag}
37 |
38 | ))}
39 |
40 |
41 | )
42 | }
43 |
44 | export default TagCloud
--------------------------------------------------------------------------------
/components/OrdinalThumbnail.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useRouter } from 'next/router'
3 | import { Figure } from 'react-bootstrap'
4 |
5 | const OrdinalThumbnail = (props) => {
6 | const router = useRouter()
7 |
8 | const handleClick = () => {
9 | router.push(`/explorer/inscription/${props.collection.id}`);
10 | }
11 |
12 | return (
13 |
14 |
15 |
22 |
23 |
{props.collection.meta.name}
24 |
25 | )
26 | }
27 |
28 | export default OrdinalThumbnail;
--------------------------------------------------------------------------------
/components/TestnetSwitch.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect } from 'react';
2 | import { ToggleButtonGroup, ToggleButton, Container } from 'react-bootstrap';
3 | import { TestnetContext } from '../contexts/TestnetContext';
4 |
5 | const TestnetSwitch = () => {
6 | const { testnet, setTestnet } = useContext(TestnetContext);
7 |
8 | const handleSelectionChange = (value) => {
9 | setTestnet(value);
10 | };
11 |
12 |
13 |
14 | useEffect(() => {
15 | handleSelectionChange(testnet);
16 | }, [testnet]);
17 |
18 | return (
19 |
20 |
21 |
22 |
23 | handleSelectionChange(false)} title="positive">
24 | Mainnet
25 |
26 | handleSelectionChange(true)} title="negative">
27 | Testnet
28 |
29 |
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default TestnetSwitch;
37 |
--------------------------------------------------------------------------------
/components/UtxoInfo.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { TailSpin } from 'react-loading-icons';
3 | import { Row, Col, Card, Container, Button, Pagination } from 'react-bootstrap';
4 | import UtxoImage from './UtxoImage';
5 | import InscriptionsDetails from './InscriptionsDetails';
6 |
7 | export default function UtxoInfo({ utxosReady, ownedUtxos, setCurrentUtxo, setShowUtxoModal, inscriptionUtxosByUtxo, testnet }) {
8 | const [activeItemId, setActiveItemId] = useState(null);
9 | const [currentPage, setCurrentPage] = useState(1);
10 | const itemsPerPage = 6;
11 |
12 | if (!utxosReady) return (
13 | <>
14 | Wallet loading...
15 |
16 |
17 |
18 | >
19 | );
20 |
21 | const handleItemClick = (item) => {
22 | setCurrentUtxo(item);
23 | setActiveItemId(item.txid);
24 | };
25 |
26 | const handlePageChange = (pageNumber) => {
27 | setCurrentPage(pageNumber);
28 | };
29 |
30 | const paginate = (array, page_size, page_number) => {
31 | return array.slice((page_number - 1) * page_size, page_number * page_size);
32 | };
33 |
34 | const pageNumbers = [];
35 | for (let i = 1; i <= Math.ceil(ownedUtxos.length / itemsPerPage); i++) {
36 | pageNumbers.push(i);
37 | }
38 |
39 | return (
40 |
41 | {ownedUtxos.length === 0 ? (
42 |
45 | ) : (
46 | <>
47 |
48 |
49 |
50 | {paginate(ownedUtxos, itemsPerPage, currentPage).map((it) => {
51 | return (
52 |
53 |
57 | {!inscriptionUtxosByUtxo[`${it.txid}:${it.vout}`] ? (
58 | <>
59 |
60 |
61 |
62 |
63 |
64 | >
65 | ) : (
66 |
67 | )}
68 |
72 |
73 |
74 | {
75 | setCurrentUtxo(it)
76 | setShowUtxoModal(true)
77 | }} >Send
78 |
79 |
80 | {/*
*/}
81 |
82 | );
83 | })}
84 |
85 |
86 | {pageNumbers.map(num => (
87 | handlePageChange(num)}>
88 | {num}
89 |
90 | ))}
91 |
92 |
93 | >
94 | )
95 | }
96 |
97 | );
98 | }
99 |
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/components/WalletConfig/connectLedger.js:
--------------------------------------------------------------------------------
1 |
2 | import { listen } from "@ledgerhq/logs";
3 | import AppBtc from "@ledgerhq/hw-app-btc";
4 | import TransportWebUSB from "@ledgerhq/hw-transport-webusb";
5 | import * as bitcoin from 'bitcoinjs-lib'
6 | import * as ecc from 'tiny-secp256k1'
7 | import * as secp256k1 from "secp256k1";
8 | import ECPairFactory from 'ecpair';
9 |
10 |
11 | const ECPair = ECPairFactory(ecc);
12 |
13 | bitcoin.initEccLib(ecc)
14 |
15 | function toXOnly(key) {
16 | return key.length === 33 ? key.slice(1, 33) : key;
17 | }
18 |
19 |
20 | export const getLedgerPubkey = async (ledgerVerify) => {
21 | let ledgerPublicKey = null;
22 | let transport = null;
23 | try {
24 | transport = await TransportWebUSB.create();
25 | console.log("Transport created", transport)
26 | const appBtc = new AppBtc({ transport, currency: "bitcoin" });
27 | const { publicKey } = await appBtc.getWalletPublicKey(
28 | "86'/0'/100'/0/0",
29 | { verify: ledgerVerify, format: "bech32m" }
30 | );
31 | const pubkeyBuffer = Buffer.from(publicKey, 'hex')
32 | const pubkey = ECPair.fromPublicKey(pubkeyBuffer)
33 | ledgerPublicKey = pubkey.publicKey.toString('hex')
34 | } catch (e) {
35 | console.log(e)
36 | console.log("Error connecting to Ledger", e.message);
37 | alert("Error connecting to Ledger", e.message || e)
38 | } finally {
39 | transport.close()
40 | return ledgerPublicKey;
41 | }
42 | }
43 |
44 |
45 |
46 | export const getAddressInfoLedger = async (ledgerPublicKey, verify, testnet) => {
47 | if (ledgerPublicKey === null) {
48 | throw new Error('Ledger public key is null');
49 | }
50 | if (verify) {
51 | const transport = await TransportWebUSB.create();
52 | try {
53 | const appBtc = new AppBtc({ transport, currency: "bitcoin" });
54 | await appBtc.getWalletPublicKey(
55 | "86'/0'/100'/0/0",
56 | { verify: true, format: "bech32m" }
57 | );
58 | } catch (e) {
59 | console.log(e)
60 | console.log("Error connecting to Ledger", e.message);
61 | alert("Error connecting to Ledger", e.message)
62 | }
63 | transport.close()
64 | }
65 | console.log(`Ledger pub: ${ledgerPublicKey}`)
66 | console.log(`Type of ledgerPublicKey: ${typeof ledgerPublicKey}`);
67 | console.log(`Value of ledgerPublicKey: ${ledgerPublicKey}`);
68 |
69 | const addrInfo = bitcoin.payments.p2tr({ internalPubkey: toXOnly(Buffer.from(ledgerPublicKey, 'hex')), network: testnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin })
70 | console.log(`addrInfo: ${addrInfo.address}`)
71 |
72 | return addrInfo
73 | }
74 |
75 |
76 | export const signLedger = async (psbt, txData) => {
77 | const transport = await TransportWebUSB.create();
78 | console.log("Transport created", transport)
79 | listen(log => console.log(log))
80 | const newTx = psbt.__CACHE.__TX;
81 | // console.log("newTx", newTx)
82 | // console.log("PSBT", psbt)
83 | // console.log("txData", txData)
84 | const path = "86'/0'/100'/0/0";
85 | const DEFAULT_LOCK_TIME = 0;
86 | const utxo = bitcoin.Transaction.fromHex(txData.txHex)
87 | console.log("utxo", utxo)
88 | try {
89 | // console.log("Creating appBtc:")
90 | // console.log("psbt cache new tx", psbt.__CACHE.__TX)
91 | const appBtc = new AppBtc({ transport, currency: "bitcoin" });
92 |
93 | const inLedgerTx = await splitTransaction(appBtc, utxo);
94 | const outLedgerTx = await splitTransaction(appBtc, psbt.__CACHE.__TX);
95 | const outputScriptHex = await appBtc.serializeTransactionOutputs(outLedgerTx).toString('hex');
96 |
97 | // console.log("outputScriptHex", outputScriptHex)
98 | // console.log("inLedgerTx", inLedgerTx)
99 | // console.log("outLedgerTx", outLedgerTx)
100 | // console.log("redem script", txData.redeemScript.toString('hex'))
101 |
102 | const ledgerTxSignatures = await appBtc.createPaymentTransaction({
103 | inputs: [[inLedgerTx, txData.inputIndex]],
104 | associatedKeysets: [path],
105 | outputScriptHex: outputScriptHex,
106 | lockTime: DEFAULT_LOCK_TIME,
107 | segwit: newTx.hasWitnesses(),
108 | additionals: ["bech32m"],
109 | transactionVersion: psbt.data.globalMap.unsignedTx.tx.version,
110 | sigHashType: bitcoin.Transaction.SIGHASH_ALL,
111 | useTrustedInputForSegwit: true,
112 | });
113 | console.log("ledgerTxSignatures", ledgerTxSignatures)
114 | return ledgerTxSignatures;
115 | } catch (e) {
116 | console.log(e)
117 | console.log(e.message);
118 | alert("Error connecting to Ledger", e.message)
119 | }
120 |
121 | function splitTransaction(ledger, tx) {
122 | return ledger.splitTransaction(tx.toHex(), tx.hasWitnesses());
123 | }
124 |
125 | }
126 |
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/components/WalletConfig/constance.js:
--------------------------------------------------------------------------------
1 | export const INSCRIPTION_SEARCH_DEPTH = 5
2 | export const DEFAULT_FEE_RATE = 7
3 | export const SENDS_ENABLED = true
4 | export const ASSUMED_TX_BYTES = 111
5 |
--------------------------------------------------------------------------------
/components/WalletConfig/hiroWalletFunctions.js:
--------------------------------------------------------------------------------
1 | import * as ecc from 'tiny-secp256k1';
2 | import * as bitcoin from 'bitcoinjs-lib';
3 | import ECPairFactory from 'ecpair';
4 | import { BIP32Factory } from 'bip32';
5 | import { Buffer } from 'buffer';
6 | const ECPair = ECPairFactory(ecc);
7 | const bip39 = require('bip39');
8 | const bip32 = BIP32Factory(ecc);
9 | bitcoin.initEccLib(ecc);
10 | import { getAddress } from 'sats-connect'
11 |
12 |
13 | function toXOnly(key) {
14 | return key.length === 33 ? key.slice(1, 33) : key;
15 | }
16 |
17 | // Module-level variables to store the address and public key
18 | let address = null;
19 | let publicKey = null;
20 |
21 |
22 |
23 | export async function connectHiro(testnet) {
24 |
25 | const addr = await window.btc?.request('getAddresses',
26 | testnet ? 'Testnet' : 'Mainnet',
27 | );
28 | console.log('addr', addr);
29 |
30 | // Find the p2tr address in the addresses array
31 | const p2trAddress = addr?.result?.addresses?.find(address => address.type === 'p2tr');
32 |
33 | console.log('p2trAddress', p2trAddress);
34 | if (p2trAddress && p2trAddress.address) {
35 | address = p2trAddress.address;
36 | publicKey = p2trAddress.publicKey;
37 | }
38 | }
39 |
40 | export const getHiroPubkey = async () => {
41 | if (!publicKey) {
42 | throw new Error("Public key not set. Make sure you've connected to Hiro.");
43 | }
44 | return publicKey;
45 | };
46 |
47 | export const getAddressInfoHiro = async () => {
48 | if (!address) {
49 | throw new Error("Address not set. Make sure you've connected to Hiro.");
50 | }
51 | return address;
52 | };
53 |
--------------------------------------------------------------------------------
/components/WalletConfig/ordimintWalletFunctions.js:
--------------------------------------------------------------------------------
1 | import * as ecc from 'tiny-secp256k1';
2 | import * as bitcoin from 'bitcoinjs-lib';
3 | import ECPairFactory from 'ecpair';
4 | import { BIP32Factory } from 'bip32';
5 | import { Buffer } from 'buffer';
6 | const ECPair = ECPairFactory(ecc);
7 | const bip39 = require('bip39');
8 | const bip32 = BIP32Factory(ecc);
9 | bitcoin.initEccLib(ecc);
10 |
11 |
12 | const crypto =
13 | typeof window !== 'undefined' && window.crypto
14 | ? window.crypto
15 | : require('crypto').webcrypto;
16 |
17 | function toXOnly(key) {
18 | return key.length === 33 ? key.slice(1, 33) : key;
19 | }
20 |
21 | export const getOrdimintAddress = async (pubkey, testnet) => {
22 | var address = null;
23 | try {
24 | address = await bitcoin.payments.p2tr({ pubkey: toXOnly(Buffer.from(pubkey, 'hex')), network: testnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin }).address;
25 |
26 | } catch (err) {
27 | console.log('Error getting address: ' + err.message);
28 | }
29 | return address;
30 | }
31 |
32 | export const generateWallet = async (testnet) => {
33 | const entropy = crypto.getRandomValues(new Uint8Array(16));
34 | const mnemonic = bip39.entropyToMnemonic(Buffer.from(entropy).toString('hex'));
35 | const seed = bip39.mnemonicToSeedSync(mnemonic);
36 | const root = bip32.fromSeed(seed);
37 | const path = "m/86'/0'/0'";
38 | const keyPair = root.derivePath(path);
39 | const newPrivateKey = keyPair.toWIF();
40 | const newOrdimintPubkey = await (Buffer.from(keyPair.publicKey, 'hex')).toString('hex');
41 | const newAddress = await bitcoin.payments.p2tr({ pubkey: toXOnly(Buffer.from(keyPair.publicKey, 'hex')), network: testnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin }).address;
42 |
43 | return {
44 | newOrdimintPubkey,
45 | newPrivateKey,
46 | newAddress,
47 | mnemonic,
48 | };
49 | };
50 |
51 |
52 |
53 | export const restoreWallet = (event, testnet) => {
54 | return new Promise(async (resolve, reject) => {
55 | let restoredPrivateKey, restoredKeyPair, restoredAddress, restoredPubkey;
56 | const file = event.target.files[0];
57 | if (!file) {
58 | reject('No file provided');
59 | return;
60 | }
61 |
62 | const reader = new FileReader();
63 | reader.onload = async (e) => {
64 | const privateKeyContent = e.target.result;
65 |
66 | const privateKeyMatch = privateKeyContent.match(/Ordimint-Key: (\S+)/);
67 |
68 | if (!privateKeyMatch || !privateKeyMatch[1]) {
69 | reject('Invalid private key file');
70 | return;
71 | }
72 |
73 | try {
74 | restoredPrivateKey = privateKeyMatch[1].trim()
75 | restoredKeyPair = ECPair.fromWIF(restoredPrivateKey)
76 | restoredAddress = await bitcoin.payments.p2tr({ pubkey: toXOnly(Buffer.from(restoredKeyPair.publicKey, 'hex')), network: testnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin }).address
77 | restoredPubkey = await Buffer.from(restoredKeyPair.publicKey, 'hex').toString('hex');
78 | resolve({ restoredPrivateKey, restoredKeyPair, restoredAddress, restoredPubkey });
79 | } catch (err) {
80 | reject('Error restoring wallet: ' + err.message);
81 | }
82 | };
83 |
84 | reader.readAsText(file);
85 | });
86 | };
87 |
88 |
89 |
--------------------------------------------------------------------------------
/components/WalletConfig/unisatWalletFunctions.js:
--------------------------------------------------------------------------------
1 | import * as ecc from 'tiny-secp256k1';
2 | import * as bitcoin from 'bitcoinjs-lib';
3 | import ECPairFactory from 'ecpair';
4 | import { BIP32Factory } from 'bip32';
5 | import { Buffer } from 'buffer';
6 | const ECPair = ECPairFactory(ecc);
7 | const bip39 = require('bip39');
8 | const bip32 = BIP32Factory(ecc);
9 | bitcoin.initEccLib(ecc);
10 |
11 |
12 |
13 | function toXOnly(key) {
14 | return key.length === 33 ? key.slice(1, 33) : key;
15 | }
16 |
17 |
18 |
19 |
20 | export const connectUnisat = async () => {
21 | let unisat = window.unisat;
22 | await unisat.requestAccounts();
23 | for (let i = 1; i < 10 && !unisat; i += 1) {
24 | await new Promise((resolve) => setTimeout(resolve, 10000 * i));
25 | unisat = window.unisat;
26 | }
27 |
28 | if (unisat) {
29 | const publicKey = await unisat.getPublicKey();
30 | console.log("publicKey", publicKey)
31 | return publicKey;
32 | } else if (!unisat)
33 | Alert("Unisat not installed", "Please install Unisat to use this feature", "error");
34 | }
35 |
36 |
37 |
38 | export const getUnisatPubkey = async (unisat) => {
39 |
40 | const publicKey = await unisat.getPublicKey();
41 | console.log("publicKey", publicKey)
42 | return publicKey;
43 | };
44 |
45 | export const getAddressInfoUnisat = async () => {
46 | let unisat = window.unisat;
47 | var address;
48 | try {
49 | address = await unisat.getAccounts();
50 | return address[0];
51 | } catch (err) {
52 | console.log('Error getting address Info: ' + err.message);
53 | }
54 |
55 | };
56 |
57 |
--------------------------------------------------------------------------------
/components/WalletConfig/utils.js:
--------------------------------------------------------------------------------
1 | import * as bitcoin from 'bitcoinjs-lib'
2 | import * as ecc from 'tiny-secp256k1'
3 |
4 | import { ASSUMED_TX_BYTES } from './constance'
5 |
6 | bitcoin.initEccLib(ecc)
7 |
8 | export const outputValue = (currentUtxo, sendFeeRate) => {
9 | return currentUtxo.value - sendFeeRate * ASSUMED_TX_BYTES
10 | }
11 |
12 | export const ordinalsUrl = (utxo, testnet) => {
13 | const baseUrl = testnet ? 'https://testnet.ordimint.com' : 'https://explorer.ordimint.com';
14 | return `${baseUrl}/output/${utxo.txid}:${utxo.vout}`;
15 | }
16 |
17 | export const ordinalsImageUrl = (utxo, testnet) => {
18 | const baseUrl = testnet ? 'https://testnet.ordimint.com' : 'https://explorer.ordimint.com';
19 | return `${baseUrl}/content/${utxo.txid}i${utxo.vout}`;
20 | }
21 |
22 | export const cloudfrontUrl = (utxo) => {
23 | return `https://d2v3k2do8kym1f.cloudfront.net/minted-items/${utxo.txid}:${utxo.vout}`
24 | }
25 |
26 | export const shortenStr = (str) => {
27 | if (!str) return ""
28 | return str.substring(0, 8) + "..." + str.substring(str.length - 8, str.length)
29 | }
30 |
31 | export const getAddressInfoNostr = (nostrPublicKey, testnet) => {
32 | const pubkeyBuffer = Buffer.from(nostrPublicKey, 'hex')
33 | const addrInfo = bitcoin.payments.p2tr({ pubkey: pubkeyBuffer, network: testnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin })
34 | return addrInfo
35 | }
36 |
37 | export const connectWallet = async () => {
38 | if (window.nostr && window.nostr.enable) {
39 | await window.nostr.enable()
40 | } else {
41 | alert("Oops, it looks like you haven't set up your Nostr key yet or you don't have the Alby browser extension. Go to your Alby Account Settings and create or import a Nostr key.")
42 | return
43 | }
44 | return await window.nostr.getPublicKey()
45 | }
46 |
--------------------------------------------------------------------------------
/components/WalletConfig/xverseWalletFunctions.js:
--------------------------------------------------------------------------------
1 | import * as ecc from 'tiny-secp256k1';
2 | import * as bitcoin from 'bitcoinjs-lib';
3 | import ECPairFactory from 'ecpair';
4 | import { BIP32Factory } from 'bip32';
5 | import { Buffer } from 'buffer';
6 | const ECPair = ECPairFactory(ecc);
7 | const bip39 = require('bip39');
8 | const bip32 = BIP32Factory(ecc);
9 | bitcoin.initEccLib(ecc);
10 | import { getAddress } from 'sats-connect'
11 |
12 |
13 | function toXOnly(key) {
14 | return key.length === 33 ? key.slice(1, 33) : key;
15 | }
16 |
17 | // Module-level variables to store the address and public key
18 | let address = null;
19 | let publicKey = null;
20 |
21 | function getAddressOptionsFunc(testnet) {
22 | return {
23 | payload: {
24 | purposes: ['ordinals', 'payment'],
25 | message: 'Address for receiving Ordinals and payments',
26 | network: {
27 | type: testnet ? 'Testnet' : 'Mainnet',
28 | },
29 | },
30 | onFinish: (response) => {
31 | console.log(response);
32 | publicKey = response.addresses[0].publicKey;
33 | address = response.addresses[0].address;
34 | console.log('address', address);
35 | console.log('publicKey', publicKey);
36 | },
37 | onCancel: () => alert('Request canceled'),
38 | };
39 | }
40 |
41 | export async function connectXverse(testnet) {
42 | await getAddress(getAddressOptionsFunc(testnet));
43 | }
44 |
45 | export const getXversePubkey = async () => {
46 | if (!publicKey) {
47 | throw new Error("Public key not set. Make sure you've connected to Xverse.");
48 | }
49 | return publicKey;
50 | };
51 |
52 | export const getAddressInfoXverse = async () => {
53 | if (!address) {
54 | throw new Error("Address not set. Make sure you've connected to Xverse.");
55 | }
56 | return address;
57 | };
58 |
--------------------------------------------------------------------------------
/components/modals/AlterModal.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Modal, Button, Alert } from "react-bootstrap";
3 |
4 | const AlertModal = (props) => {
5 | if (!props.show) {
6 | return null;
7 | }
8 | return (
9 | <>
10 |
11 |
12 | Alert
13 |
14 |
15 | {props.text}
16 |
17 |
18 | Ok
19 |
20 |
21 |
22 |
23 | >
24 | );
25 | };
26 |
27 | export default AlertModal;
28 |
--------------------------------------------------------------------------------
/components/modals/AuctionModal.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import SelectWallet from './AuctionSteps/SelectWallet';
3 | import SelectOrdinal from './AuctionSteps/SelectOrdinal';
4 | import CreateOffer from './AuctionSteps/CreateOffer';
5 | import { Modal, Button, Alert } from 'react-bootstrap';
6 | import PayOffer from './AuctionSteps/PayOffer';
7 |
8 | const steps = [
9 | { name: 'Connect Wallet', component: (props) => },
10 | { name: 'Select your Ordinal', component: (props) => },
11 | { name: 'Create offer', component: (props) => },
12 | { name: 'Pay for listing', component: (props) => },
13 | ];
14 |
15 | var isPaid = false;
16 |
17 | function AuctionModal(props) {
18 | if (!props.show) {
19 | return null;
20 | }
21 |
22 | const [currentStep, setCurrentStep] = useState(0);
23 | const [selectedWallet, setSelectedWallet] = useState('Ordimint');
24 | const [currentUtxo, setCurrentUtxo] = useState(null)
25 | const [inscriptionData, setInscriptionData] = useState(null)
26 | const [clientPaymentHash, setClientPaymentHash] = useState(null)
27 | const [invoice, setInvoice] = useState({})
28 | const [isPSBTsigned, setIsPSBTsigned] = useState(false)
29 | const [price, setPrice] = useState(process.env.REACT_APP_default_auction_price)
30 |
31 | const signPSBT = async () => {
32 | try {
33 | await props.socket.emit("signPSBT", { inscriptionID: inscriptionData.id, price: price });
34 | }
35 | catch (e) {
36 | console.log(e)
37 | }
38 | }
39 |
40 |
41 |
42 | // useEffect(() => {
43 | // console.log(currentUtxo);
44 | // }, [currentUtxo]);
45 |
46 | const createOrder = async () => {
47 | try {
48 | await props.socket.emit("getInvoice", price);
49 |
50 | }
51 | catch (e) {
52 | console.log(e)
53 | }
54 |
55 | }
56 |
57 | props.socket.off("connect").on("connect", () => {
58 | /////Checks for already paid invoice if browser switche tab on mobile
59 | if (clientPaymentHash !== undefined) {
60 | console.log("check invoice");
61 | console.log(clientPaymentHash);
62 | checkInvoice();
63 | }
64 | });
65 |
66 | const checkInvoice = () => {
67 | props.socket.emit("checkInvoice", clientPaymentHash);
68 | };
69 |
70 | props.socket.on("lnbitsInvoice", (invoiceData) => {
71 | setInvoice(invoiceData.payment_request);
72 | setClientPaymentHash(invoiceData.payment_hash);
73 | // console.log(invoiceData);
74 | });
75 |
76 | props.socket.off("invoicePaid").on("invoicePaid", async (paymentHash) => {
77 | if (paymentHash === clientPaymentHash && isPaid === false) {
78 | isPaid = true;
79 | await props.socket.emit("createPSBT", { inscriptionID: inscriptionData.id, paymentHash: paymentHash });
80 | console.log("invoice paid");
81 | }
82 | });
83 |
84 |
85 | const handleNext = () => {
86 | if (currentStep < steps.length - 1) {
87 | setCurrentStep(currentStep + 1);
88 | }
89 | if (currentStep === 1 && currentUtxo === null) {
90 | setShowAlert(true);
91 | }
92 | };
93 |
94 | const handleBack = () => {
95 | if (currentStep > 0) {
96 | setCurrentStep(currentStep - 1);
97 | }
98 | };
99 |
100 | const CurrentComponent = steps[currentStep].component;
101 |
102 | return (
103 | <>
104 |
109 |
110 |
111 | {steps[currentStep].name}
112 |
113 |
114 |
125 |
126 |
127 |
128 |
129 |
130 | {currentStep !== 0 && (
131 | Back
132 | )}
133 | {currentStep < steps.length - 2 ? (
134 | Next
137 | ) : currentStep === steps.length - 2 && (
138 | {
140 | await createOrder();
141 | handleNext();
142 | }}
143 | disabled={!isPSBTsigned}
144 | >Create Offer
145 |
146 |
147 | )}
148 |
149 |
150 |
151 | >
152 | );
153 | }
154 |
155 | export default AuctionModal;
156 |
--------------------------------------------------------------------------------
/components/modals/AuctionSteps/AlbyWalletAuction.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useState, useEffect } from 'react';
3 | import axios from 'axios';
4 | import { restoreWallet } from '../../WalletConfig/ordimintWalletFunctions.js'
5 | import { Container, Button } from 'react-bootstrap';
6 | import { BsBoxArrowInDownLeft } from "react-icons/bs"
7 | import UtxoInfoAuction from './UtxoInfoAuction.js';
8 | import ReceiveAddressModal from '../ReceiveAddressModal';
9 | import { getAddressInfoNostr, connectWallet } from '../../WalletConfig/utils.js';
10 | import { TestnetContext } from '../../../contexts/TestnetContext.js';
11 |
12 |
13 | const AlbyWalletAuction = (props) => {
14 | const { testnet } = React.useContext(TestnetContext);
15 | const [nostrPublicKey, setNostrPublicKey] = useState(null);
16 | const [showReceiveAddressModal, setShowReceiveAddressModal] = useState(false);
17 | const [ownedUtxos, setOwnedUtxos] = useState([]);
18 | const [utxosReady, setUtxosReady] = useState(false)
19 | const [inscriptionUtxosByUtxo, setInscriptionUtxosByUtxo] = useState({})
20 |
21 | useEffect(() => {
22 | async function fetchUtxosForAddress() {
23 | if (!nostrPublicKey) return
24 | const address = getAddressInfoNostr(nostrPublicKey, testnet).address
25 | const mempoolUrl = testnet ? 'https://mempool.space/testnet/api' : 'https://mempool.space/api';
26 | const response = await axios.get(`${mempoolUrl}/address/${address}/utxo`)
27 | const tempInscriptionsByUtxo = {}
28 | setOwnedUtxos(response.data)
29 | for (const utxo of response.data) {
30 | tempInscriptionsByUtxo[`${utxo.txid}:${utxo.vout}`] = utxo
31 | // if (!utxo.status.confirmed) continue
32 | let currentUtxo = utxo
33 | // console.log('utxo', utxo)
34 |
35 | // console.log(`Checking utxo ${currentUtxo.txid}:${currentUtxo.vout}`)
36 | try {
37 | const explorerUrl = testnet ? 'https://testnet.ordimint.com' : 'https://explorer.ordimint.com';
38 | const res = await axios.get(`${explorerUrl}/output/${currentUtxo.txid}:${currentUtxo.vout}`)
39 | const inscriptionId = res.data.match(//)?.[1]
40 | const [txid, vout] = inscriptionId.split('i')
41 | currentUtxo = { txid, vout }
42 | } catch (err) {
43 | console.log(`Error from explorer.ordimint.com: ${err}`)
44 | }
45 | tempInscriptionsByUtxo[`${utxo.txid}:${utxo.vout}`] = currentUtxo
46 | const newInscriptionsByUtxo = {}
47 | Object.assign(newInscriptionsByUtxo, tempInscriptionsByUtxo)
48 | setInscriptionUtxosByUtxo(newInscriptionsByUtxo)
49 | setUtxosReady(true)
50 | }
51 | setInscriptionUtxosByUtxo(tempInscriptionsByUtxo)
52 | setUtxosReady(true)
53 | }
54 |
55 | connectOnLoad()
56 | fetchUtxosForAddress()
57 | }, [nostrPublicKey]);
58 |
59 | async function connectOnLoad() {
60 | setNostrPublicKey(await connectWallet())
61 | }
62 |
63 |
64 |
65 | return (
66 |
67 |
68 |
69 | {nostrPublicKey &&
70 |
71 |
78 |
79 | }
80 |
81 |
82 |
83 |
84 |
91 |
92 | )
93 | }
94 |
95 | export default AlbyWalletAuction
96 |
--------------------------------------------------------------------------------
/components/modals/AuctionSteps/CreateOffer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useState, useEffect } from 'react'
3 | import InputGroup from 'react-bootstrap/InputGroup'
4 | import Form from 'react-bootstrap/Form'
5 | import { TailSpin } from 'react-loading-icons';
6 | import Button from 'react-bootstrap/Button'
7 | import AlertModal from '../AlterModal'
8 | const CreateOffer = (props) => {
9 |
10 | //////Alert - Modal
11 | const [alertModalparams, showAlertModal] = useState({
12 | show: false,
13 | text: "",
14 | type: "",
15 | });
16 | const hideAlertModal = () =>
17 | showAlertModal({ show: false, text: "", type: "" });
18 |
19 | const noPriceSet = () => {
20 | showAlertModal({
21 | show: true,
22 | text: "Please provide a price for your offer",
23 | type: "danger",
24 | });
25 | }
26 |
27 |
28 | useEffect(() => {
29 | getInscriptionData(props.currentUtxo)
30 | }, [])
31 |
32 | async function getInscriptionData(utxo) {
33 | try {
34 | const response = await fetch(`https://ordapi.xyz/output/${utxo.txid}:${utxo.vout}`)
35 | const inscriptionPerOutput = await response.json()
36 | const response2 = await fetch(`https://ordapi.xyz${inscriptionPerOutput.inscriptions}`)
37 | const response2JSON = await response2.json()
38 | props.setInscriptionData(response2JSON)
39 | console.log(response2JSON)
40 |
41 | }
42 | catch (e) {
43 | console.log(e)
44 | }
45 |
46 | }
47 |
48 | return (
49 | <> {props.inscriptionData ?
50 | <>
51 | Content:
52 |
53 |
54 |
55 |
Owner Address:
56 | {props.inscriptionData.address}
57 |
58 |
59 |
Inscription ID:
60 | {props.inscriptionData.id}
61 |
62 |
63 | Inscription Number: {props.inscriptionData.inscription_number}
64 |
65 |
66 | > :
67 |
68 | Loading...
69 | }
70 |
71 |
72 | Price
73 | props.setPrice(e.target.value)}
76 | />
77 | Sats
78 |
79 | Sign with your wallet
83 |
84 |
85 |
86 |
92 | >
93 | )
94 | }
95 |
96 | export default CreateOffer
97 |
--------------------------------------------------------------------------------
/components/modals/AuctionSteps/InscriptionsDetailsAuction.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { useState } from 'react'
3 |
4 | const InscriptionsDetailsAuction = (props) => {
5 |
6 | const [inscriptionData, setInscriptionData] = useState(null)
7 |
8 |
9 | useEffect(() => {
10 | getInscriptionData(props.utxo)
11 | }, [])
12 |
13 | async function getInscriptionData(utxo) {
14 | try {
15 | const response = await fetch(`https://ordapi.xyz/output/${utxo.txid}:${utxo.vout}`)
16 | const inscriptionPerOutput = await response.json()
17 | const response2 = await fetch(`https://ordapi.xyz${inscriptionPerOutput.inscriptions}`)
18 | const response2JSON = await response2.json()
19 | setInscriptionData(response2JSON)
20 | console.log(response2JSON)
21 |
22 | }
23 | catch (e) {
24 | console.log(e)
25 | }
26 |
27 | }
28 | return (
29 | <>
30 | {
31 | props.testnet ? <>>
32 | :
33 |
34 |
35 | {inscriptionData ?
36 |
#{inscriptionData.inscription_number}
:
37 |
38 |
39 |
40 | Loading...
41 | }
42 |
43 | }
44 | >
45 | )
46 | }
47 |
48 | export default InscriptionsDetailsAuction
49 |
--------------------------------------------------------------------------------
/components/modals/AuctionSteps/LedgerWalletAuction.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const LedgerWalletAuction = () => {
4 | return (
5 |
8 | )
9 | }
10 |
11 | export default LedgerWalletAuction
12 |
--------------------------------------------------------------------------------
/components/modals/AuctionSteps/OrdimintWalletAuction.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useState, useEffect } from 'react';
3 | import axios from 'axios';
4 | import { restoreWallet } from '../../WalletConfig/ordimintWalletFunctions.js'
5 | import { Container, Button } from 'react-bootstrap';
6 | import { BsBoxArrowInDownLeft } from "react-icons/bs"
7 | import UtxoInfoAuction from './UtxoInfoAuction.js';
8 | import ReceiveAddressModal from '../ReceiveAddressModal';
9 | import { TestnetContext } from '../../../contexts/TestnetContext.js';
10 |
11 |
12 | const OrdimintWalletAuction = () => {
13 | const { testnet } = React.useContext(TestnetContext);
14 | const [ordimintPubkey, setOrdimintPubkey] = useState(null);
15 | const [privateKey, setPrivateKey] = useState(null);
16 | const [address, setAddress] = useState(null);
17 | const [ownedUtxos, setOwnedUtxos] = useState([]);
18 | const [utxosReady, setUtxosReady] = useState(false)
19 | const [inscriptionUtxosByUtxo, setInscriptionUtxosByUtxo] = useState({})
20 |
21 | const [showReceiveAddressModal, setShowReceiveAddressModal] = useState(false);
22 |
23 |
24 | useEffect(() => {
25 | async function fetchUtxosForAddress() {
26 | if (!address) return
27 | const mempoolUrl = testnet ? 'https://mempool.space/testnet/api' : 'https://mempool.space/api';
28 | const response = await axios.get(`${mempoolUrl}/address/${address}/utxo`)
29 | const tempInscriptionsByUtxo = {}
30 | setOwnedUtxos(response.data)
31 | for (const utxo of response.data) {
32 | tempInscriptionsByUtxo[`${utxo.txid}:${utxo.vout}`] = utxo
33 | // if (!utxo.status.confirmed) continue
34 | let currentUtxo = utxo
35 | console.log('utxo', utxo)
36 |
37 | console.log(`Checking utxo ${currentUtxo.txid}:${currentUtxo.vout}`)
38 | try {
39 | const explorerUrl = testnet ? 'https://testnet.ordimint.com' : 'https://explorer.ordimint.com';
40 | const res = await axios.get(`${explorerUrl}/output/${currentUtxo.txid}:${currentUtxo.vout}`)
41 | const inscriptionId = res.data.match(/ /)?.[1]
42 | const [txid, vout] = inscriptionId.split('i')
43 | currentUtxo = { txid, vout }
44 | } catch (err) {
45 | console.log(`Error from explorer.ordimint.com: ${err}`)
46 | }
47 | tempInscriptionsByUtxo[`${utxo.txid}:${utxo.vout}`] = currentUtxo
48 | const newInscriptionsByUtxo = {}
49 | Object.assign(newInscriptionsByUtxo, tempInscriptionsByUtxo)
50 | setInscriptionUtxosByUtxo(newInscriptionsByUtxo)
51 | setUtxosReady(true)
52 | }
53 | setInscriptionUtxosByUtxo(tempInscriptionsByUtxo)
54 | setUtxosReady(true)
55 | }
56 |
57 |
58 | fetchUtxosForAddress()
59 | }, [ordimintPubkey, address]);
60 |
61 | const handleRestoreWallet = async (event, testnet) => {
62 | try {
63 | const { restoredAddress, restoredPubkey, restoredPrivateKey } = await restoreWallet(event, testnet);
64 | setAddress(restoredAddress);
65 | setPrivateKey(restoredPrivateKey);
66 | setOrdimintPubkey(restoredPubkey);
67 |
68 | } catch (error) {
69 | console.error('Error:', error);
70 |
71 | }
72 | };
73 |
74 |
75 | return (
76 |
77 |
78 | {/* Ordimint Wallet */}
79 | {
80 | address ?
81 |
82 | {/*
84 | setShowReceiveAddressModal(true)}>
85 | Receive
86 | */}
87 |
88 | :
89 | <>
90 |
91 |
92 | handleRestoreWallet(event)}
98 | />
99 | document.getElementById('restoreWalletFile').click()}>
100 | Choose Backup File
101 |
102 |
103 |
104 |
105 | >
106 | }
107 |
108 | {address &&
109 |
110 |
118 |
}
119 |
120 |
127 |
128 | )
129 | }
130 |
131 | export default OrdimintWalletAuction
132 |
--------------------------------------------------------------------------------
/components/modals/AuctionSteps/PayOffer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { QRCodeCanvas } from 'qrcode.react'
3 |
4 | const PayOffer = (props) => {
5 | return (
6 |
53 | )
54 | }
55 |
56 | export default PayOffer
57 |
--------------------------------------------------------------------------------
/components/modals/AuctionSteps/SelectOrdinal.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { useState } from 'react';
3 | import { Spinner } from 'react-bootstrap';
4 |
5 | // Import your wallet components
6 | import OrdimintWalletAuction from './OrdimintWalletAuction';
7 | import AlbyWalletAuction from './AlbyWalletAuction';
8 | import LedgerWalletAuction from './LedgerWalletAuction';
9 |
10 | const SelectOrdinal = (props) => {
11 |
12 | const [showSpinner, setShowSpinner] = useState(true);
13 | const [ordinal, setOrdinal] = useState(0);
14 | const [publicKey, setPublicKey] = useState('');
15 |
16 | useEffect(() => {
17 | props.setCurrentUtxo(null);
18 | if (props.selectedWallet === 'Ordimint') {
19 | // Do something
20 | }
21 | else if (props.selectedWallet === 'Alby') {
22 | // Do something
23 | }
24 | else if (props.selectedWallet === 'Ledger') {
25 | // Do something
26 | }
27 |
28 | }, [props.selectedWallet]);
29 |
30 | const renderWallet = () => {
31 | switch (props.selectedWallet) {
32 | case 'Ordimint':
33 | return ;
36 | case 'Alby':
37 | return ;
40 | case 'Ledger':
41 | return ;
43 | default:
44 | return Could not connect to wallet
;
45 | }
46 | };
47 |
48 | return (
49 | <>
50 | {
51 | renderWallet()
52 | }
53 | >
54 | )
55 | }
56 |
57 | export default SelectOrdinal;
58 |
--------------------------------------------------------------------------------
/components/modals/AuctionSteps/SelectWallet.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { ButtonGroup, ToggleButton } from 'react-bootstrap';
3 | import Image from 'next/image';
4 | import AlbyLogo from '../../../public/media/alby_icon_yellow.svg';
5 | import LedgerLogo from '../../../public/media/ledger-logo-small.svg';
6 | import OrdimintLogo from '../../../public/media/ordimint-coin-white.png';
7 |
8 | const wallets = [
9 | { logo: OrdimintLogo, name: 'Ordimint', alt: 'Ordimint Logo' },
10 | { logo: AlbyLogo, name: 'Alby', alt: 'Alby Logo' },
11 | { logo: LedgerLogo, name: 'Ledger', alt: 'Ledger Logo' },
12 | ];
13 |
14 | const SelectWallet = (props) => {
15 | const handleWalletSelect = (value) => {
16 | props.setSelectedWallet(value);
17 | };
18 |
19 | return (
20 |
21 |
22 |
23 | {wallets.map((wallet, index) => (
24 | handleWalletSelect(e.currentTarget.value)}
33 |
34 | >
35 |
36 | {" "}
37 | {wallet.name} Wallet
38 |
39 | ))}
40 |
41 |
42 |
43 | );
44 | };
45 |
46 | export default SelectWallet;
47 |
--------------------------------------------------------------------------------
/components/modals/AuctionSteps/UtxoImageAuction.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { ordinalsImageUrl, cloudfrontUrl } from "../../WalletConfig/utils"
3 | import { parseTextInscription } from '../../../public/functions/ordinalFunctions'
4 | import { Figure, Button } from 'react-bootstrap'
5 | import { useState } from 'react'
6 |
7 | export default function UtxoImageAuction({ utxo, style, inscriptionUtxosByUtxo, testnet }) {
8 |
9 | const [isText, setIsText] = useState(false)
10 | const [text, setText] = useState("")
11 |
12 | function renderJsonData(jsonData) {
13 | // Handle different p flags
14 | switch (jsonData.pFlag) {
15 | case "ons":
16 | return (
17 | <>
18 | News
19 | Title: {jsonData.title}
20 | {/* URL: {jsonData.url}
21 | Author: {jsonData.author}
22 | Body: {jsonData.body.length > 20 ? jsonData.body.substring(0, 20) + '...' : jsonData.body}
*/}
23 |
24 | >
25 | );
26 |
27 | case "sns":
28 | return (
29 | <>
30 | .sats Domain
31 | {jsonData.name}
32 | >
33 | );
34 |
35 | case "brc-20":
36 | if (jsonData.op === "mint") {
37 | return (
38 |
39 | <>
40 | BRC-20 {jsonData.op}
41 | Ticker: {jsonData.tick}
42 | Amount: {jsonData.amt}
43 | >
44 |
45 | );
46 | } else if (jsonData.op === "transfer") {
47 | return (
48 |
49 | <>
50 | BRC-20 {jsonData.op}
51 | Ticker: {jsonData.tick}
52 | Amount: {jsonData.amt}
53 | >
54 |
55 | );
56 | }
57 | else if (jsonData.op === "deploy") {
58 | return (
59 |
60 | <>
61 | BRC-20 {jsonData.op}
62 | Ticker: {jsonData.tick}
63 | Amount: {jsonData.amt}
64 | >
65 |
66 | );
67 | }
68 |
69 | default:
70 | return Unknown operation.
;
71 | }
72 | }
73 |
74 |
75 | async function setContentType(utxo, testnet) {
76 |
77 | const contentURL = await ordinalsImageUrl(inscriptionUtxosByUtxo[`${utxo.txid}:${utxo.vout}`], testnet)
78 | const response = await fetch(contentURL)
79 | const contentType = response.headers.get('content-type')
80 | if (contentType.includes("text")) {
81 | const text = await response.text()
82 | const parsedText = parseTextInscription(text)
83 | if (parsedText.pFlag) {
84 | setText(renderJsonData(parsedText))
85 | }
86 | else {
87 | setText(parsedText)
88 | }
89 | setIsText(true)
90 |
91 | }
92 | }
93 |
94 | useEffect(() => {
95 | setContentType(utxo, testnet)
96 | }, [utxo])
97 |
98 |
99 | return (
100 | <>
101 | {
102 | isText ? (
103 | {text}
104 |
) :
105 | (
106 |
107 |
113 |
114 |
115 |
116 | )
117 | }
118 | >
119 |
120 | )
121 | }
122 |
--------------------------------------------------------------------------------
/components/modals/AuctionSteps/UtxoInfoAuction.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { TailSpin } from 'react-loading-icons';
3 | import { Row, Col, Card, Container, Button, Pagination } from 'react-bootstrap';
4 | import UtxoImageAuction from './UtxoImageAuction';
5 | import InscriptionsDetails from '../../InscriptionsDetails';
6 |
7 | export default function UtxoInfoAuction({ utxosReady, ownedUtxos, setCurrentUtxo, inscriptionUtxosByUtxo, testnet }) {
8 | const [activeItemId, setActiveItemId] = useState(null);
9 | const [currentPage, setCurrentPage] = useState(1);
10 | const itemsPerPage = 6;
11 |
12 | if (!utxosReady) return (
13 | <>
14 | Wallet loading...
15 |
16 |
17 |
18 | >
19 | );
20 |
21 | const handleItemClick = (item) => {
22 | setCurrentUtxo(item);
23 | setActiveItemId(item.txid);
24 | };
25 |
26 | const handlePageChange = (pageNumber) => {
27 | setCurrentPage(pageNumber);
28 | };
29 |
30 | const paginate = (array, page_size, page_number) => {
31 | return array.slice((page_number - 1) * page_size, page_number * page_size);
32 | };
33 |
34 | const pageNumbers = [];
35 | for (let i = 1; i <= Math.ceil(ownedUtxos.length / itemsPerPage); i++) {
36 | pageNumbers.push(i);
37 | }
38 |
39 | return (
40 |
41 | {ownedUtxos.length === 0 ? (
42 |
45 | ) : (
46 | <>
47 |
48 |
49 |
50 | {paginate(ownedUtxos, itemsPerPage, currentPage).map((it) => {
51 | const isActiveItem = activeItemId === it.txid;
52 | return (
53 |
54 | handleItemClick(it)}
58 | >
59 | {!inscriptionUtxosByUtxo[`${it.txid}:${it.vout}`] ? (
60 | <>
61 |
62 |
63 |
64 |
65 |
66 | >
67 | ) : (
68 |
69 | )}
70 |
74 |
75 |
76 | );
77 | })}
78 |
79 |
80 | {pageNumbers.map(num => (
81 | handlePageChange(num)}>
82 | {num}
83 |
84 | ))}
85 |
86 |
87 | >
88 | )}
89 |
90 | );
91 | }
92 |
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/components/modals/BeginSendModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Modal from 'react-bootstrap/Modal'
3 | import Button from 'react-bootstrap/Button'
4 | import InputGroup from 'react-bootstrap/InputGroup'
5 | import Form from 'react-bootstrap/Form'
6 | import UtxoImage from '../UtxoImage'
7 | import { useState } from 'react'
8 | import { shortenStr } from "../WalletConfig//utils"
9 | import { validate, Network } from 'bitcoin-address-validation'
10 | import Html5QrcodePlugin from '../Html5QrcodeScannerPlugin'
11 |
12 | export default function BeginSendModal({
13 | showBeginSendModal,
14 | setShowBeginSendModal,
15 | currentUtxo,
16 | setIsBtcInputAddressValid,
17 | setDestinationBtcAddress,
18 | setShowSelectFeeRateModal,
19 | isBtcInputAddressValid,
20 | inscriptionUtxosByUtxo,
21 | testnet,
22 | setShowUtxoModal
23 | }) {
24 |
25 | const [btcAddress, setBtcAddress] = useState("");
26 |
27 |
28 | const onNewScanResult = (decodedText, decodedResult) => {
29 | if (validate(decodedText, testnet ? Network.testnet : Network.mainnet)) {
30 | setBtcAddress(decodedText);
31 | setIsBtcInputAddressValid(true);
32 | setDestinationBtcAddress(decodedText);
33 | setShowBeginSendModal(false);
34 | setShowSelectFeeRateModal(true);
35 | } else {
36 | setIsBtcInputAddressValid(false);
37 | }
38 | };
39 |
40 |
41 |
42 |
43 | return (
44 | { setShowBeginSendModal(false) }} className="py-5">
45 |
46 | Send {shortenStr(currentUtxo && `${currentUtxo.txid}:${currentUtxo.vout}`)}
47 |
48 |
49 |
50 | {currentUtxo && }
51 |
52 | Where would you like to send this to?
53 |
54 | {
57 | const newaddr = evt.target.value;
58 | setBtcAddress(newaddr);
59 | if (newaddr === '') {
60 | setIsBtcInputAddressValid(true);
61 | return;
62 | }
63 | if (!validate(newaddr, testnet ? Network.testnet : Network.mainnet)) {
64 | setIsBtcInputAddressValid(false);
65 | return;
66 | }
67 | setDestinationBtcAddress(newaddr);
68 | setShowBeginSendModal(false);
69 | setShowSelectFeeRateModal(true);
70 | }}
71 | placeholder="Paste BTC address here"
72 | aria-label="Paste BTC address heres"
73 | aria-describedby="basic-addon2"
74 | isInvalid={!isBtcInputAddressValid}
75 | id="btc-address-input-send-modal"
76 | autoFocus
77 | />
78 |
79 |
80 | That is not a valid {testnet ? 'testnet' : 'mainnet'} BTC address
81 |
82 |
83 |
89 |
90 |
91 |
92 | {
93 | setShowBeginSendModal(false)
94 | }}>
95 | Cancel
96 |
97 | {
98 | setShowUtxoModal(true)
99 | setShowBeginSendModal(false)
100 | }}>
101 | Back
102 |
103 | { setShowBeginSendModal(true) }}>
104 | Send
105 |
106 |
107 |
108 | )
109 | }
110 |
--------------------------------------------------------------------------------
/components/modals/GenerateWalletModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Modal, Button } from 'react-bootstrap';
3 | import { IoIosCopy } from "react-icons/io";
4 | import { useState, useEffect } from 'react';
5 |
6 |
7 |
8 | const GenerateWalletModal = ({
9 | showModal,
10 | closeModal,
11 | seedPhrase,
12 | privateKey,
13 |
14 | }) => {
15 |
16 |
17 |
18 | const [seedDownloaded, setSeedDownloaded] = useState(false);
19 |
20 |
21 | const copySeedPhrase = async () => {
22 | if (!seedPhrase) {
23 | alert('Please generate a wallet first!');
24 | return;
25 | }
26 | try {
27 | await navigator.clipboard.writeText(seedPhrase);
28 | alert('Seed phrase copied to clipboard!');
29 | } catch (err) {
30 | console.error('Failed to copy seed phrase: ', err);
31 | alert('Failed to copy seed phrase. Please try again.');
32 | }
33 | };
34 |
35 | const handleDownloadPrivateKey = () => {
36 | downloadPrivateKey(privateKey);
37 | setSeedDownloaded(true);
38 | };
39 |
40 |
41 | const handleCloseAndDownload = () => {
42 | if (!seedDownloaded) {
43 | alert("Please download your backup file!")
44 | downloadPrivateKey(privateKey);
45 | closeModal();
46 | }
47 | else {
48 | closeModal();
49 | }
50 | };
51 |
52 | const downloadPrivateKey = (privateKey) => {
53 | if (!privateKey) {
54 | alert('Please generate a wallet first!');
55 | return;
56 | }
57 |
58 | const fileContent = `Ordimint-Key: ${privateKey}\n`;
59 | const blob = new Blob([fileContent], { type: 'text/plain;charset=utf-8' });
60 | const url = URL.createObjectURL(blob);
61 | const link = document.createElement('a');
62 | link.href = url;
63 | link.download = 'ordimint-backup.txt';
64 | document.body.appendChild(link);
65 | link.click();
66 |
67 | setTimeout(() => {
68 | document.body.removeChild(link);
69 | URL.revokeObjectURL(url);
70 | }, 100);
71 | };
72 |
73 |
74 | return (
75 |
76 |
77 | Wallet Backup
78 |
79 |
80 |
81 |
Here's your seed phrase:
82 |
83 | {seedPhrase}
84 |
85 |
86 |
87 |
88 |
89 | Important: Write down this seed phrase safely! It is the
90 | only way to recover your wallet if you lose your backup file.
91 |
92 |
93 |
94 |
95 | Download Backup File
96 |
97 |
98 | Close
99 |
100 |
101 |
102 | );
103 | };
104 |
105 | export default GenerateWalletModal;
106 |
107 |
108 |
--------------------------------------------------------------------------------
/components/modals/OrdimintWalletInfo.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Button, Modal } from 'react-bootstrap';
3 |
4 | const OrdimintWalletInfo = (props) => {
5 |
6 | if (!props.show) {
7 | return null;
8 | }
9 |
10 | return (
11 | <>
12 |
13 |
14 |
15 | Ordimint Wallet
16 |
17 |
18 |
19 | This is a simple Ordimint Wallet that allows you to create, restore, and manage your non-custodial wallet. The wallet can be used to receive Ordinals and send Ordinals to other addresses.
20 |
21 |
22 | To get started, you can either generate a new wallet or restore an existing wallet from a backup file. Once your wallet is set up, you will be able to see your wallet address for receiving Ordinals.
23 |
24 |
25 | Please make sure to back up your wallet by writing down the provided seed phrase and downloading the backup file. The seed phrase is essential for recovering your wallet in case you lose the backup file.
26 |
27 |
28 | If you need to restore your wallet, just use the "Restore Wallet" option and select your backup file.
29 |
30 |
31 | You can view your Ordinals and when you want to send it to another wallet, simply follow the on-screen instructions to enter the destination address, select a fee rate, and confirm the transaction. Please make sure that the other wallet is Ordinal ready.
32 |
33 |
34 | Remember to always keep your seed phrase and backup file safe, as they are the only ways to recover your wallet in case of loss or damage.
35 |
36 |
37 |
38 |
39 | Close
40 |
41 |
42 |
43 | >
44 | );
45 | };
46 |
47 | export default OrdimintWalletInfo;
48 |
--------------------------------------------------------------------------------
/components/modals/ReceiveAddressModal.js:
--------------------------------------------------------------------------------
1 | import Modal from 'react-bootstrap/Modal';
2 | import Button from 'react-bootstrap/Button';
3 | import { useState, useEffect } from 'react';
4 | import { getAddressInfoNostr } from '../WalletConfig/utils';
5 | import { getAddressInfoLedger } from '../WalletConfig/connectLedger';
6 | import { QRCodeCanvas, } from "qrcode.react";
7 |
8 | export default function ReceiveAddressModal({ showReceiveAddressModal,
9 | setShowReceiveAddressModal, nostrPublicKey, ledgerPublicKey, ordimintAddress, address, testnet }) {
10 |
11 | const [ledgerAddress, setLedgerAddress] = useState(null)
12 |
13 |
14 | useEffect(() => {
15 | if (!ledgerPublicKey) return
16 | async function getLedgerAddress() {
17 | setLedgerAddress(await (await getAddressInfoLedger(ledgerPublicKey, false, testnet)).address)
18 | }
19 | getLedgerAddress()
20 |
21 | }, [ledgerPublicKey, testnet])
22 |
23 | useEffect(() => {
24 | if (!showReceiveAddressModal || !ledgerPublicKey) return
25 | async function verifyAddress() {
26 | await getAddressInfoLedger(ledgerPublicKey, true, testnet).address
27 |
28 | }
29 | verifyAddress()
30 | }
31 | , [showReceiveAddressModal, testnet])
32 |
33 | function getAddressForQRCode() {
34 | if (nostrPublicKey) {
35 | return getAddressInfoNostr(nostrPublicKey, testnet).address;
36 | } else if (ledgerPublicKey) {
37 | return ledgerAddress;
38 | } else if (ordimintAddress) {
39 | return ordimintAddress;
40 | }
41 | else if (address) {
42 | return address;
43 | }
44 | return '';
45 | }
46 |
47 | return (
48 | setShowReceiveAddressModal(false)} className="py-5">
49 |
50 | Receive Address
51 |
52 |
53 | {getAddressForQRCode()}
54 |
55 |
56 |
57 |
58 |
59 | {ledgerPublicKey && verify your address on your ledger device }
60 |
61 | (you can safely receive ordinal inscriptions and regular bitcoin to this address)
62 |
63 |
64 | {
65 | if (nostrPublicKey) {
66 | navigator.clipboard.writeText(getAddressInfoNostr(nostrPublicKey, testnet).address)
67 | }
68 | if (ledgerPublicKey) {
69 | navigator.clipboard.writeText(ledgerAddress)
70 | }
71 | if (ordimintAddress) {
72 | navigator.clipboard.writeText(ordimintAddress)
73 | }
74 | if (address) {
75 | navigator.clipboard.writeText(address)
76 | }
77 | setShowReceiveAddressModal(false)
78 | }}>Copy Address
79 |
80 |
81 | )
82 | }
83 |
--------------------------------------------------------------------------------
/components/modals/RestoreWalletModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Modal, Button } from 'react-bootstrap';
3 |
4 |
5 | const RestoreWalletModal = ({ showRestoreWalletModal, handleRestoreWalletModalClose, restoreWallet, testnet }) => {
6 | return (
7 |
8 |
9 | Restore Wallet
10 |
11 |
12 |
13 | restoreWallet(event, testnet)}
19 | />
20 | document.getElementById('restoreWalletFile').click()}>
21 | Choose Backup File
22 |
23 |
24 |
25 |
26 |
27 | Close
28 |
29 |
30 |
31 | );
32 | };
33 |
34 | export default RestoreWalletModal;
35 |
36 |
37 |
--------------------------------------------------------------------------------
/components/modals/SelectFeeRateModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Modal from 'react-bootstrap/Modal';
3 | import Button from 'react-bootstrap/Button';
4 | import Form from 'react-bootstrap/Form';
5 | import { outputValue, shortenStr } from '../WalletConfig/utils';
6 | import UtxoImage from '../UtxoImage';
7 |
8 | export default function SelectFeeRateModal({
9 | showSelectFeeRateModal,
10 | setShowSelectFeeRateModal,
11 | currentUtxo,
12 | sendFeeRate,
13 | setSendFeeRate,
14 | setShowBeginSendModal,
15 | setShowConfirmSendModal,
16 | inscriptionUtxosByUtxo,
17 | testnet,
18 | }) {
19 | return (
20 | setShowSelectFeeRateModal(false)} className="py-5">
21 |
22 | Sending {shortenStr(currentUtxo && `${currentUtxo.txid}:${currentUtxo.vout}`)}
23 |
24 |
25 |
26 | {currentUtxo && }
27 |
28 |
29 | Select a fee rate
30 |
31 | setSendFeeRate(evt.target.value)} />
32 |
33 | {sendFeeRate} sat/vbyte
34 |
35 |
36 | Output Value : {currentUtxo && sendFeeRate && outputValue(currentUtxo, sendFeeRate)} sats
37 |
38 |
39 |
40 | {
41 | setShowSelectFeeRateModal(false)
42 | }}>
43 | Cancel
44 |
45 | {
46 | setShowSelectFeeRateModal(false)
47 | setShowBeginSendModal(true)
48 | }}>
49 | Back
50 |
51 | {
52 | setShowSelectFeeRateModal(false);
53 | setShowConfirmSendModal(true);
54 | }}>
55 | Next
56 |
57 |
58 |
59 | )
60 | }
61 |
--------------------------------------------------------------------------------
/components/modals/SelectWalletModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Modal, Button } from 'react-bootstrap';
3 |
4 | const SelectWalletModal = ({ show, handleClose, handleGenerateWallet, handleRestoreWallet }) => {
5 | return (
6 |
7 |
8 | Ordimint Wallet
9 |
10 |
11 | Generate a new wallet or restore an existing one.
12 |
13 |
14 | Generate Wallet
15 |
16 |
17 |
18 | Restore Wallet
19 |
20 |
21 |
22 |
23 |
24 | Close
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | export default SelectWalletModal;
32 |
--------------------------------------------------------------------------------
/components/modals/SentModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Modal from 'react-bootstrap/Modal';
3 | import Button from 'react-bootstrap/Button';
4 |
5 | export default function SentModal({ showSentModal, setShowSentModal, sentTxid, testnet }) {
6 | const txUrl = `https://mempool.space/${testnet ? 'testnet/' : ''}tx/${sentTxid}`;
7 | return (
8 | setShowSentModal(false)} className="py-5">
9 |
10 | Success
11 |
12 |
13 |
14 | Your transaction should appear in a few moments here
15 |
16 |
17 |
18 | {
19 | setShowSentModal(false)
20 | }}>
21 | Nice
22 |
23 |
24 |
25 | )
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/components/modals/SingleOrdinalModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Modal, Button, Figure } from 'react-bootstrap';
3 | import { useEffect, useState } from 'react';
4 | // import { getInscriptionNumber } from "../../functions/ordinalFunctions.js";
5 | const SingleOrdinalModal = (props) => {
6 |
7 | // const [inscriptionID, setInscriptionID] = useState("")
8 |
9 | // useEffect(() => {
10 |
11 | // setInscriptionID(getInscriptionNumber(props.selectedOrdinal))
12 |
13 | // }, [props.selectedOrdinal])
14 |
15 | if (!props.show) {
16 | return null;
17 | }
18 |
19 | return (
20 |
21 |
22 | < div >
23 |
24 |
25 | {props.selectedOrdinalName}
26 |
27 |
28 |
29 |
36 |
37 | {/*
38 | {inscriptionID ? inscriptionID : <>>}
39 | */}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | Close
49 |
50 |
51 |
52 |
53 |
54 | )
55 | }
56 |
57 | export default SingleOrdinalModal
58 |
--------------------------------------------------------------------------------
/components/modals/UtxoModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Modal from 'react-bootstrap/Modal';
3 | import Button from 'react-bootstrap/Button';
4 | import { ordinalsUrl, shortenStr } from '../WalletConfig/utils';
5 | import UtxoImage from '../UtxoImage';
6 |
7 |
8 | export default function UtxoModal({
9 | setShowBeginSendModal,
10 | showUtxoModal,
11 | setShowUtxoModal,
12 | currentUtxo,
13 | inscriptionUtxosByUtxo,
14 | SENDS_ENABLED,
15 | testnet
16 | }) {
17 | return (
18 | { setShowUtxoModal(false) }} className="py-5">
19 |
20 | {shortenStr(currentUtxo && `${currentUtxo.txid}:${currentUtxo.vout}`)}:{currentUtxo && currentUtxo.vout}
21 |
22 |
23 |
24 | {currentUtxo && }
25 |
26 | Utxo:
27 |
34 |
35 | Value: {currentUtxo && currentUtxo.value.toLocaleString('en-US')} sats
36 |
37 |
38 |
39 | { setShowUtxoModal(false) }}>
40 | Cancel
41 |
42 | {SENDS_ENABLED && {
43 | setShowUtxoModal(false)
44 | setShowBeginSendModal(true)
45 | }}> Send }
46 |
47 |
48 | )
49 | }
50 |
--------------------------------------------------------------------------------
/components/modals/WalletConnectModal.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Modal, Button, Alert, Container, Form, InputGroup, Overlay, Tooltip } from "react-bootstrap";
3 | import { IoIosCopy } from "react-icons/io";
4 | import { useState, useRef } from "react";
5 |
6 |
7 |
8 |
9 | const WalletConnectModal = (props) => {
10 |
11 | const [showTooltipID, setShowTooltipID] = useState(false);
12 | const targetID = useRef(null);
13 |
14 | const renderTooltipID = (show) => {
15 | setShowTooltipID(show);
16 | setTimeout(() => setShowTooltipID(false), [1000]);
17 | };
18 |
19 | if (!props.show) {
20 | return null;
21 | }
22 |
23 |
24 | return (
25 | <>
26 |
27 |
28 | Wallet connected!
29 |
30 |
31 |
32 | {props.text}
33 |
34 |
35 | Your receiver address is:
36 |
37 |
39 |
40 |
41 |
46 | {
50 | navigator.clipboard.writeText(props.address);
51 | renderTooltipID(!showTooltipID);
52 | }}
53 | variant="primary"
54 |
55 | >
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | You can safely receive ordinal inscriptions and regular Bitcoin to this address.
66 |
67 |
68 |
69 |
70 | Ok
71 |
72 |
73 |
74 |
75 |
76 |
82 | {(propsTooltip) => (
83 |
84 | Copied!
85 |
86 | )}
87 |
88 | >
89 | )
90 | };
91 |
92 | export default WalletConnectModal
93 |
--------------------------------------------------------------------------------
/contexts/TestnetContext.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useState } from 'react';
2 |
3 | export const TestnetContext = createContext();
4 |
5 | export const TestnetProvider = ({ children }) => {
6 | const [testnet, setTestnet] = useState(false);
7 |
8 | return (
9 |
10 | {children}
11 |
12 | );
13 | };
14 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withTM = require('next-transpile-modules')(['react-bootstrap']);
2 |
3 | module.exports = withTM({
4 | webpack: (config) => {
5 | // Enable async WebAssembly
6 | config.experiments = {
7 | asyncWebAssembly: true,
8 | };
9 |
10 | // Set the module type for .wasm files
11 | config.module.rules.push({
12 | test: /\.wasm$/,
13 | type: 'webassembly/async',
14 | });
15 |
16 | return config;
17 | },
18 | async rewrites() {
19 | return [
20 | {
21 | source: '/invoicehook',
22 | destination: 'http://localhost:5000/invoicehook',
23 | }]
24 | },
25 | async redirects() {
26 | return [
27 | {
28 | source: '/collections/:path*',
29 | destination: '/ordinal-collections/:path*',
30 | permanent: true,
31 | },
32 | ]
33 | }, images: {
34 | domains: ['explorer.ordimint.com', 'testnet.ordimint.com'],
35 | },
36 | });
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "main": "index.js",
5 | "private": true,
6 | "dependencies": {
7 | "@ledgerhq/hw-app-btc": "^10.0.0",
8 | "@ledgerhq/hw-transport-webusb": "^6.27.12",
9 | "@ledgerhq/logs": "^6.10.1",
10 | "@react-icons/all-files": "^4.1.0",
11 | "@testing-library/jest-dom": "^5.16.5",
12 | "@testing-library/react": "^13.4.0",
13 | "@testing-library/user-event": "^13.5.0",
14 | "antd": "^5.8.0",
15 | "axios": "^1.2.6",
16 | "babel-plugin-inline-dotenv": "^1.7.0",
17 | "bip32": "^3.1.0",
18 | "bip39": "^3.1.0",
19 | "bitbox02-api": "^0.15.0",
20 | "bitcoin-address-validation": "^2.2.1",
21 | "bitcoinjs-lib": "^6.1.0",
22 | "bootstrap": "^5.1.3",
23 | "browser-image-compression": "^2.0.2",
24 | "copy-webpack-plugin": "^11.0.0",
25 | "dayjs": "^1.11.7",
26 | "ecpair": "^2.1.0",
27 | "next": "^13.3.0",
28 | "next-transpile-modules": "^10.0.0",
29 | "node-polyfill-webpack-plugin": "^2.0.1",
30 | "nostr-tools": "^1.2.1",
31 | "qrcode.react": "^3.0.1",
32 | "react": "^18.2.0",
33 | "react-bootstrap": "^2.4.0",
34 | "react-bootstrap-range-slider": "^3.0.8",
35 | "react-countdown": "^2.3.2",
36 | "react-dom": "^18.2.0",
37 | "react-dropzone": "^14.2.3",
38 | "react-dropzone-uploader": "^2.11.0",
39 | "react-helmet": "^6.1.0",
40 | "react-icons": "^4.7.1",
41 | "react-loading-icons": "^1.1.0",
42 | "react-rotating-text": "^1.4.1",
43 | "react-slide-fade-in": "^1.0.7",
44 | "react-social-icons": "^5.15.0",
45 | "react-useinterval": "^1.0.2",
46 | "sats-connect": "^0.5.0",
47 | "secp256k1": "^5.0.0",
48 | "socket.io-client": "^4.4.1",
49 | "tiny-secp256k1": "^2.2.1",
50 | "web-vitals": "^2.1.4",
51 | "ws": "^8.12.1"
52 | },
53 | "scripts": {
54 | "dev": "next dev",
55 | "build": "next build",
56 | "start": "next start",
57 | "deploy": "npm run build && sudo systemctl restart next-app",
58 | "start-babel": "NODE_ENV=development webpack serve",
59 | "build-babel": "npm run clean; NODE_ENV=production webpack --color; cp -f _redirects build/"
60 | },
61 | "eslintConfig": {
62 | "extends": [
63 | "react-app",
64 | "react-app/jest"
65 | ]
66 | },
67 | "browserslist": {
68 | "production": [
69 | ">0.2%",
70 | "not dead",
71 | "not op_mini all"
72 | ],
73 | "development": [
74 | "last 1 chrome version",
75 | "last 1 firefox version",
76 | "last 1 safari version"
77 | ]
78 | },
79 | "devDependencies": {
80 | "@babel/core": "^7.21.0",
81 | "@babel/preset-env": "^7.20.2",
82 | "@babel/preset-react": "^7.18.6",
83 | "autoprefixer": "10.4.5",
84 | "babel-loader": "^9.1.2",
85 | "css-loader": "^6.3.0",
86 | "globby": "^11.0.1",
87 | "html-webpack-plugin": "^5.3.2",
88 | "html5-qrcode": "^2.3.8",
89 | "mini-css-extract-plugin": "^2.4.2",
90 | "postcss-loader": "^6.1.1",
91 | "postcss-preset-env": "^6.7.0",
92 | "sass": "^1.42.1",
93 | "sass-loader": "^10.2.0",
94 | "sparkpost": "^2.1.4",
95 | "style-loader": "^3.3.0",
96 | "url-loader": "^4.1.1",
97 | "webpack": "^5.58.0",
98 | "webpack-cli": "^4.9.0",
99 | "webpack-dev-server": "^4.3.1"
100 | },
101 | "alias": {
102 | "@ledgerhq/devices": "@ledgerhq/devices/lib-es"
103 | }
104 | }
--------------------------------------------------------------------------------
/pages/404.js:
--------------------------------------------------------------------------------
1 | export default function Custom404() {
2 | return (
3 |
4 |
No Ordinals found. Nothing to see here. Move on!
5 | )
6 | }
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '../public/index.css'
2 | import '../public/font.css'
3 | import '../public/theme.css'
4 |
5 | import { SSRProvider } from 'react-bootstrap';
6 | import Header from '../components/Header.js';
7 | import { useEffect } from 'react'
8 | import { useRouter } from 'next/router'
9 | import { TestnetProvider } from '../contexts/TestnetContext.js'
10 | import Footer from '../components/Footer';
11 |
12 | export default function MyApp({ Component, pageProps }) {
13 | const router = useRouter()
14 | const GA_ID = process.env.REACT_APP_GA_ID;
15 |
16 | useEffect(() => {
17 | if (!GA_ID) return;
18 |
19 | const handleRouteChange = (url) => {
20 | window.gtag('config', GA_ID, {
21 | page_path: url,
22 | })
23 | }
24 |
25 | router.events.on('routeChangeComplete', handleRouteChange)
26 |
27 | return () => {
28 | router.events.off('routeChangeComplete', handleRouteChange)
29 | }
30 | }, [router.events])
31 |
32 | return (
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | )
45 | }
46 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Document, { Html, Head, Main, NextScript } from 'next/document'
2 |
3 | class MyDocument extends Document {
4 | static async getInitialProps(ctx) {
5 | const initialProps = await Document.getInitialProps(ctx)
6 | return { ...initialProps }
7 | }
8 |
9 | render() {
10 | const GA_ID = process.env.REACT_APP_GA_ID; // Get Google Analytics ID
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | {/*
22 | */}
23 |
24 | {GA_ID && (
25 | <>
26 |
27 |
28 |
39 | >
40 | )}
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | )
49 | }
50 | }
51 |
52 | export default MyDocument
53 |
--------------------------------------------------------------------------------
/pages/_error.js:
--------------------------------------------------------------------------------
1 | function Error({ statusCode }) {
2 | return (
3 |
4 | {statusCode
5 | ? `An error ${statusCode} occurred on server`
6 | : 'An error occurred on client'}
7 |
8 | )
9 | }
10 |
11 | Error.getInitialProps = ({ res, err }) => {
12 | const statusCode = res ? res.statusCode : err ? err.statusCode : 404
13 | return { statusCode }
14 | }
15 |
16 | export default Error
--------------------------------------------------------------------------------
/pages/check-order.js:
--------------------------------------------------------------------------------
1 | import { React, useState } from 'react'
2 | import { InputGroup, Form, Container, Button, Figure, Col, Row } from 'react-bootstrap'
3 | import { useRouter } from 'next/router';
4 |
5 | import axios from 'axios'
6 | import Head from 'next/head';
7 | const CheckOrder = () => {
8 | const router = useRouter()
9 | const [orderID, setOrderID] = useState("");
10 | const [orderStatus, setOrderStatus] = useState("");
11 | const [testnet, setTestnet] = useState(false)
12 | const [txhash, setTxhash] = useState("");
13 | const [inscriptionID, setInscriptionID] = useState("");
14 | const [orderSource, setOrderSource] = useState("");
15 |
16 |
17 | const checkOrder = async () => {
18 |
19 |
20 | if (orderID === "") {
21 | return;
22 | }
23 |
24 | await axios.get(
25 | `${process.env.REACT_APP_BACKEND_API}${orderID}`
26 | ).then(response => {
27 | setOrderStatus(response.data.status);
28 | if (response.data.mintingTransaction) {
29 | setTxhash(response.data.mintingTransaction);
30 | }
31 | if (response.data.inscription_ID) {
32 | setInscriptionID(response.data.inscription_ID);
33 | }
34 | if (response.data.testnet) {
35 | setTestnet(true);
36 | }
37 | setOrderSource(response.data.source);
38 | })
39 |
40 | // if error
41 | .catch(function (error) {
42 | setOrderStatus("Error: Order not found")
43 |
44 | });
45 | }
46 |
47 |
48 | return (
49 |
50 |
51 | Ordimint - Check Order
52 |
53 |
54 |
55 |
56 | router.back()}>< Back
57 |
58 |
59 |
60 | {/*
Check your order */}
61 |
Status: {orderStatus}
62 | {txhash && (
63 |
64 |
69 |
70 | {orderSource === "Order" && inscriptionID && (
71 |
76 | )}
77 |
78 |
79 | {orderSource === "Order" ? " Your inscription will be minted directly to your BTC address" : ""}
80 |
81 |
82 | )}
83 |
84 |
85 |
86 |
87 | Order ID
88 | setOrderID(e.target.value)}
90 | placeholder="Insert Your Order ID"
91 | aria-label="Order ID"
92 |
93 | aria-describedby="basic-addon1"
94 | />
95 |
96 |
97 |
98 |
99 |
checkOrder()}
102 | >
103 | Check Order Status
104 |
105 |
106 |
107 |
108 | )
109 | }
110 |
111 | export default CheckOrder
112 |
--------------------------------------------------------------------------------
/pages/explorer/block/[slug].js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Container, Row, Col } from 'react-bootstrap';
3 | import OrdinalGrid from '../../../components/OrdinalExplorer/OrdinalGrid';
4 | // import TagCloud from '../../components/OrdinalExplorer/TagCloud';
5 | import BlockCloud from '../../../components/OrdinalExplorer/BlockCloud';
6 | import Head from 'next/head';
7 | // import BlockStats from '../../components/OrdinalExplorer/BlockStats';
8 |
9 |
10 | const explorerURL = process.env.REACT_APP_MAINNET_URL;
11 |
12 | export async function getBlockHeight() {
13 | try {
14 | const blockHeight = await fetch('https://mempool.space/api/blocks/tip/height');
15 | const blockHeightJSON = await blockHeight.json();
16 | return blockHeightJSON;
17 | } catch (error) {
18 | console.error(error);
19 | }
20 | }
21 |
22 |
23 |
24 | export async function getOrdinalsList(block) {
25 |
26 |
27 | try {
28 | const response = await fetch(`${explorerURL}/inscriptions/block/${block}`, {
29 | headers: {
30 | 'Accept': 'application/json'
31 | }
32 |
33 | });
34 |
35 | if (!response.ok) {
36 | throw new Error(`HTTP error! Status: ${response.status}`);
37 | }
38 |
39 | if (response.headers.get('content-type').includes('application/json')) {
40 | const data = await response.json();
41 | // console.log('Data in getOrdinalsList:', data.inscriptions); // Check the data
42 | return data.inscriptions
43 |
44 | }
45 | } catch (error) {
46 | console.error('Fetch error:', error);
47 | return {
48 | props: {},
49 | };
50 | }
51 | }
52 |
53 | export async function fetchOrdinalData(ordinalId) {
54 | // console.log('Fetching data for ordinal ID:', ordinalId); // Check the ordinal ID
55 | try {
56 | const response = await fetch(`${explorerURL}/inscription/${ordinalId}`, {
57 | headers: {
58 | 'Accept': 'application/json'
59 | }
60 | });
61 | // console.log('Response:', response);
62 | if (!response.ok) {
63 | throw new Error(`HTTP error! Status: ${response.status}`);
64 | }
65 |
66 | if (response.headers.get('content-type').includes('application/json')) {
67 | const responseJSON = await response.json();
68 | return {
69 | ordinalData: responseJSON,
70 | timestamp: responseJSON.timestamp,
71 | inscription_id: responseJSON.inscription_id,
72 | inscription_number: responseJSON.inscription_number,
73 | content_type: responseJSON.content_type
74 | };
75 | }
76 | } catch (error) {
77 | console.error(error);
78 | }
79 | }
80 |
81 | async function fetchAllOrdinalsData(ordinals) {
82 | // console.log('Ordinals in fetchAllOrdinalsData:', ordinals); // Check the ordinals array
83 | const data = await Promise.all(ordinals.map(fetchOrdinalData));
84 | // console.log('Data in fetchAllOrdinalsData:', data); // Check the fetched data
85 | return data;
86 | }
87 |
88 |
89 |
90 | export async function getServerSideProps(context) {
91 | const { slug } = context.params;
92 | const newestBlockHeight = await getBlockHeight();
93 | let ordinalsData = null, inscriptionsList = null, errorMessage = null;
94 | try {
95 | inscriptionsList = await getOrdinalsList(slug);
96 | ordinalsData = await fetchAllOrdinalsData(inscriptionsList);
97 | }
98 | catch (error) {
99 | errorMessage = error.message;
100 | }
101 |
102 | return { props: { ordinalsData, blockHeight: newestBlockHeight, slug, errorMessage } };
103 | }
104 |
105 |
106 | // Explorer component
107 | const BlockDetailPage = ({ ordinalsData, blockHeight, slug, errorMessage }) => {
108 |
109 | if (errorMessage) {
110 | return
111 |
No Ordinals could be found here. Nothing to see here. Please move on!
112 |
113 | ;
114 |
115 | }
116 |
117 | return (
118 |
119 |
120 |
Ordimint Explorer - Block {slug}
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | Ordinals Explorer
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 | {/* */}
145 |
146 | {/* */}
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
)
156 |
157 | }
158 |
159 |
160 | export default BlockDetailPage
161 |
--------------------------------------------------------------------------------
/pages/explorer/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Container, Row, Col } from 'react-bootstrap';
3 | import OrdinalGrid from '../../components/OrdinalExplorer/OrdinalGrid';
4 | import TagCloud from '../../components/OrdinalExplorer/TagCloud';
5 | import BlockCloud from '../../components/OrdinalExplorer/BlockCloud';
6 | import Head from 'next/head';
7 |
8 |
9 | const explorerURL = process.env.REACT_APP_MAINNET_URL;
10 |
11 | export async function getBlockHeight() {
12 | try {
13 | const blockHeight = await fetch('https://mempool.space/api/blocks/tip/height');
14 | const blockHeightJSON = await blockHeight.json();
15 | return blockHeightJSON;
16 | } catch (error) {
17 | console.error(error);
18 | }
19 | }
20 |
21 |
22 |
23 | export async function getOrdinalsList(block) {
24 |
25 |
26 | try {
27 | const response = await fetch(`${explorerURL}/inscriptions/block/${block}`, {
28 | headers: {
29 | 'Accept': 'application/json'
30 | }
31 |
32 | });
33 |
34 | if (!response.ok) {
35 | throw new Error(`HTTP error! Status: ${response.status}`);
36 | }
37 |
38 | if (response.headers.get('content-type').includes('application/json')) {
39 | const data = await response.json();
40 | // console.log('Data in getOrdinalsList:', data.inscriptions); // Check the data
41 | return data.inscriptions
42 |
43 | }
44 | } catch (error) {
45 | console.error('Fetch error:', error);
46 | return {
47 | props: {},
48 | };
49 | }
50 | }
51 |
52 | export async function fetchOrdinalData(ordinalId) {
53 | // console.log('Fetching data for ordinal ID:', ordinalId); // Check the ordinal ID
54 | try {
55 | const response = await fetch(`${explorerURL}/inscription/${ordinalId}`, {
56 | headers: {
57 | 'Accept': 'application/json'
58 | }
59 | });
60 | // console.log('Response:', response);
61 | if (!response.ok) {
62 | throw new Error(`HTTP error! Status: ${response.status}`);
63 | }
64 |
65 | if (response.headers.get('content-type').includes('application/json')) {
66 | const responseJSON = await response.json();
67 | return {
68 | ordinalData: responseJSON,
69 | timestamp: responseJSON.timestamp,
70 | inscription_id: responseJSON.inscription_id,
71 | inscription_number: responseJSON.inscription_number,
72 | content_type: responseJSON.content_type
73 | };
74 | }
75 | } catch (error) {
76 | console.error(error);
77 | }
78 | }
79 |
80 | async function fetchAllOrdinalsData(ordinals) {
81 | // console.log('Ordinals in fetchAllOrdinalsData:', ordinals); // Check the ordinals array
82 | const data = await Promise.all(ordinals.map(fetchOrdinalData));
83 | // console.log('Data in fetchAllOrdinalsData:', data); // Check the fetched data
84 | return data;
85 | }
86 |
87 |
88 |
89 | export async function getServerSideProps(context) {
90 | const newestBlockHeight = await getBlockHeight();
91 |
92 | // Fetch the data from the server
93 | const inscriptionsList = await getOrdinalsList(newestBlockHeight);
94 | const ordinalsData = await fetchAllOrdinalsData(inscriptionsList);
95 |
96 | return { props: { ordinalsData, blockHeight: newestBlockHeight } };
97 | }
98 |
99 |
100 | // Explorer component
101 | const explorer = ({ ordinalsData, blockHeight }) => {
102 |
103 | return (
104 |
105 |
106 |
Ordimint - Ordinals Explorer
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | Ordinals Explorer
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | {/* */}
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
)
141 |
142 | }
143 |
144 |
145 | export default explorer
146 |
--------------------------------------------------------------------------------
/pages/market/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useState } from 'react'
3 | import { Container, Row, Col, Button } from 'react-bootstrap'
4 | import { io } from "socket.io-client";
5 | import AuctionModal from '../../components/modals/AuctionModal'
6 | import AuctionContainer from '../../components/AuctionContainer';
7 | import TestnetSwitch from '../../components/TestnetSwitch';
8 |
9 | var socket = io.connect(process.env.REACT_APP_socket_port_auctions);
10 | var clientPaymentHash;
11 | var isPaid = false; //Is only necessary in the case of socket event is fireing multible times
12 |
13 |
14 |
15 | const AuctionsPage = () => {
16 | ////Auction Modal
17 | const [visibleAuctionModal, setShowAuctionModal] = useState(false);
18 | const closeAuctionModal = () => setShowAuctionModal(false);
19 | const showAuctionModal = () => setShowAuctionModal(true);
20 |
21 | const [payment_request, setPaymentrequest] = useState(0);
22 |
23 | const [price, setPrice] = useState(2);
24 |
25 |
26 | return (
27 |
28 |
Marketplace
29 |
30 |
31 |
32 |
33 | {
35 | showAuctionModal();
36 | isPaid = false;
37 | }}
38 | price={price}
39 | variant="success"
40 | size="md"
41 | >Sell something
42 |
43 |
44 |
45 |
46 |
47 |
52 |
53 | )
54 | }
55 | export default AuctionsPage
56 |
--------------------------------------------------------------------------------
/pages/ordinal-collections.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Container, Row, Col, Form } from 'react-bootstrap';
3 | import CollectionThumbnail from '../components/CollectionThumbnail';
4 | import Pagination from 'react-bootstrap/Pagination';
5 | import { collections as collectionsData } from '../public/data/collections.js';
6 | import Head from 'next/head';
7 |
8 | const CollectionsCatalog = ({ collections }) => {
9 | const [search, setSearch] = useState('');
10 | const [currentPage, setCurrentPage] = useState(1);
11 | const [collectionsPerPage] = useState(20);
12 |
13 | const filterCollections = (collections, query) => {
14 | if (!query) {
15 | return collections;
16 | }
17 | return collections.filter((collection) =>
18 | collection.name.toLowerCase().includes(query.toLowerCase())
19 | );
20 | };
21 |
22 | const filteredCollections = filterCollections(collections, search);
23 |
24 | const indexOfLastCollection = currentPage * collectionsPerPage;
25 | const indexOfFirstCollection = indexOfLastCollection - collectionsPerPage;
26 | const currentCollections = filteredCollections.slice(
27 | indexOfFirstCollection,
28 | indexOfLastCollection
29 | );
30 |
31 | const paginate = (pageNumber) => setCurrentPage(pageNumber);
32 |
33 | const pageNumbers = [];
34 | for (
35 | let i = 1;
36 | i <= Math.ceil(filteredCollections.length / collectionsPerPage);
37 | i++
38 | ) {
39 | pageNumbers.push(i);
40 | }
41 |
42 | return (
43 |
44 |
45 |
46 |
Ordimint - Ordinal Collections
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | Collections
62 | setSearch(e.target.value)}
69 | />
70 |
71 |
72 | {currentCollections.map((collection, index) => (
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | ))}
81 |
82 |
83 |
84 |
85 | paginate(1)} />
86 | paginate(currentPage - 1)}
88 | disabled={currentPage === 1}
89 | />
90 | {pageNumbers.map((number) => {
91 | if (
92 | number === 1 ||
93 | number === currentPage ||
94 | number === pageNumbers.length ||
95 | (number >= currentPage - 1 && number <= currentPage + 1)
96 | ) {
97 | return (
98 | paginate(number)}
102 | >
103 | {number}
104 |
105 | );
106 | } else if (number === currentPage - 2 || number === currentPage + 2) {
107 | return (
108 |
109 | );
110 | } else {
111 | return null;
112 | }
113 | })}
114 | paginate(currentPage + 1)}
116 | disabled={currentPage === pageNumbers.length}
117 | />
118 | paginate(pageNumbers.length)} />
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 | );
127 | };
128 |
129 | export async function getStaticProps() {
130 | // Ensure the data to be passed is serializable
131 | const collections = JSON.parse(JSON.stringify(collectionsData));
132 |
133 | return {
134 | props: {
135 | collections,
136 | },
137 | };
138 | }
139 |
140 |
141 | export default CollectionsCatalog;
142 |
--------------------------------------------------------------------------------
/public/Ordimint-Twitter-card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ordimint/web-app/07dca1cf27b68d110babfa625c1060a48571e91a/public/Ordimint-Twitter-card.png
--------------------------------------------------------------------------------
/public/OrdimintSVGLogo-black.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/public/OrdimintSVGLogo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ordimint/web-app/07dca1cf27b68d110babfa625c1060a48571e91a/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ordimint/web-app/07dca1cf27b68d110babfa625c1060a48571e91a/public/favicon.ico
--------------------------------------------------------------------------------
/public/font.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "Jost-Regular";
3 | src: local("Jost-Regular"),
4 | url("./fonts/Jost-Regular.ttf") format("truetype");
5 | font-weight: light;
6 | }
7 |
--------------------------------------------------------------------------------
/public/fonts/Jost-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ordimint/web-app/07dca1cf27b68d110babfa625c1060a48571e91a/public/fonts/Jost-Regular.ttf
--------------------------------------------------------------------------------
/public/functions/auctionFunctions.js:
--------------------------------------------------------------------------------
1 | const bitcoin = require('bitcoinjs-lib')
2 | const { networks, Psbt } = bitcoin
3 |
4 | // Seller's details
5 | const seller = {
6 | privateKey: 'cRwq3Nq5C5xSpYHDGSDkXTpdtpFhAVbFET8SmG1sgutnKoGcF8Cg',
7 | address: 'n1MzXjK5sF4h8cUy3nMjKNSqWZtJYxhRj7'
8 | }
9 |
10 | // Buyer's details
11 | const buyer = {
12 | privateKey: 'cRwq3Nq5C5xSpYHDGSDkXTpdtpFhAVbFET8SmG1sgutnKoGcF8Cg',
13 | address: 'n1MzXjK5sF4h8cUy3nMjKNSqWZtJYxhRj7'
14 | }
15 |
16 | // Auction starting price in satoshis
17 | let auctionPrice = 100000
18 |
19 | // Mainnet or testnet
20 | const network = networks.testnet
21 |
22 | const sellerKeyPair = bitcoin.ECPair.fromWIF(seller.privateKey, network)
23 | const sellerP2wpkh = bitcoin.payments.p2wpkh({ pubkey: sellerKeyPair.publicKey, network })
24 |
25 | const buyerKeyPair = bitcoin.ECPair.fromWIF(buyer.privateKey, network)
26 |
27 | // Function to create PSBT
28 | function createPSBT(inputTxId, inputVout, price, buyerAddress) {
29 | const psbt = new Psbt({ network })
30 |
31 | psbt.addInput({
32 | hash: inputTxId,
33 | index: inputVout,
34 | witnessUtxo: {
35 | script: sellerP2wpkh.output,
36 | value: auctionPrice,
37 | }
38 | })
39 |
40 | psbt.addOutput({
41 | address: buyerAddress,
42 | value: price,
43 | })
44 |
45 | // Seller signs PSBT
46 | psbt.signInput(0, sellerKeyPair)
47 |
48 | return psbt
49 | }
50 |
51 | // Buyer signs the PSBT
52 | function signPSBT(psbt) {
53 | psbt.signInput(0, buyerKeyPair)
54 | psbt.finalizeAllInputs()
55 |
56 | // Broadcast the transaction
57 | const txHex = psbt.extractTransaction().toHex()
58 | console.log(`Finalized transaction hex: ${txHex}`)
59 |
60 | // In real-world application, you would broadcast the transaction to the Bitcoin network here
61 | }
62 |
63 | // Example of creating a new PSBT for a potential buyer
64 | let psbt = createPSBT('inputTransactionId', 0, auctionPrice, buyer.address)
65 |
66 | // Buyer decides to sign the PSBT and finalize the auction
67 | signPSBT(psbt)
68 |
--------------------------------------------------------------------------------
/public/functions/ordinalFunctions.js:
--------------------------------------------------------------------------------
1 | const axios = require('axios');
2 | require('dotenv').config();
3 |
4 | let baseUrl = process.env.REACT_APP_API_BASE_URL;
5 |
6 | async function getContentType(inscriptionId) {
7 | try {
8 |
9 | const response = await fetch(`${baseUrl}/inscription/${inscriptionId}`);
10 | if (!response.ok) {
11 | throw new Error(`HTTP error! Status: ${response.status}`);
12 | }
13 | const responseJSON = await response.json();
14 | return responseJSON.content_type;
15 | } catch (error) {
16 | console.error(error);
17 | return null;
18 | }
19 | }
20 |
21 |
22 |
23 |
24 |
25 |
26 | async function getInscriptionData(utxo) {
27 |
28 | try {
29 | const response = await axios.get(`https://ordapi.xyz/output/${utxo.txid}:${utxo.vout}`);
30 | console.log(response)
31 | const inscriptionPerOutput = response.data;
32 | console.log(response.data)
33 | if (!inscriptionPerOutput.inscriptions) {
34 | console.error("Inscriptions not found in response:", inscriptionPerOutput);
35 | return;
36 | }
37 |
38 | const response2 = await axios.get(`https://ordapi.xyz${inscriptionPerOutput.inscriptions}`);
39 | return response2.data;
40 | } catch (e) {
41 | console.error("There was an error fetching the data:", e.message);
42 | console.error(e);
43 | }
44 | }
45 |
46 |
47 |
48 |
49 | async function getInscriptionNumber(inscriptionId) {
50 | try {
51 | const response = await fetch(`${baseUrl}/inscription/${inscriptionId}`);
52 | if (!response.ok) {
53 | throw new Error(`HTTP error! Status: ${response.status}`);
54 | }
55 | const responseJSON = await response.json();
56 | console.log(responseJSON)
57 | return responseJSON.inscription_number;
58 | } catch (error) {
59 | console.error(error);
60 | return null;
61 | }
62 | }
63 |
64 |
65 | function parseTextInscription(jsonStr) {
66 | let jsonObj;
67 |
68 | // Try parsing the JSON string
69 | try {
70 | jsonObj = JSON.parse(jsonStr);
71 | } catch (e) {
72 | // If an error is thrown, the JSON is invalid, so just return the original string
73 | return jsonStr;
74 | }
75 |
76 | // Handle different p flags
77 | switch (jsonObj.p) {
78 | case "ons":
79 | return {
80 | pFlag: "ons",
81 | op: jsonObj.op,
82 | title: jsonObj.title,
83 | url: jsonObj.url,
84 | author: jsonObj.author,
85 | body: jsonObj.body
86 | };
87 |
88 | case "sns":
89 | return {
90 | pFlag: "sns",
91 | op: jsonObj.op,
92 | name: jsonObj.name
93 | };
94 |
95 | case "brc-20":
96 | switch (jsonObj.op) {
97 | case "deploy":
98 | return {
99 | pFlag: "brc-20",
100 | op: jsonObj.op,
101 | tick: jsonObj.tick,
102 | max: jsonObj.max,
103 | lim: jsonObj.lim
104 | };
105 | // ... handle other brc-20 ops if they exist
106 | default:
107 | return {
108 | pFlag: "brc-20",
109 | op: jsonObj.op,
110 | tick: jsonObj.tick,
111 | amt: jsonObj.amt
112 | };
113 | }
114 | case "tap":
115 | switch (jsonObj.op) {
116 | case "token-send":
117 | return {
118 | pFlag: "tap",
119 | op: jsonObj.op,
120 | items: jsonObj.items
121 | };
122 | case "token-deploy":
123 | case "token-mint":
124 | case "token-transfer":
125 | return {
126 | pFlag: "tap",
127 | op: jsonObj.op,
128 | tick: jsonObj.tick,
129 | amt: jsonObj.amt || null, // only present in some operations
130 | max: jsonObj.max || null, // only present in token-deploy
131 | lim: jsonObj.lim || null // only present in token-deploy
132 | };
133 | default:
134 | return jsonStr; // or some default object for unrecognized operations
135 | }
136 |
137 | default:
138 | // If p flag is not recognized, return the original string
139 | return jsonStr;
140 | }
141 | }
142 |
143 |
144 |
145 |
146 | export { getInscriptionNumber, getInscriptionData, getContentType, parseTextInscription }
147 |
--------------------------------------------------------------------------------
/public/logo-dark.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ordimint/web-app/07dca1cf27b68d110babfa625c1060a48571e91a/public/logo-dark.jpeg
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ordimint/web-app/07dca1cf27b68d110babfa625c1060a48571e91a/public/logo.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
--------------------------------------------------------------------------------
/public/media/HiroWalletLogo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ordimint/web-app/07dca1cf27b68d110babfa625c1060a48571e91a/public/media/HiroWalletLogo.jpg
--------------------------------------------------------------------------------
/public/media/LeatherWalletLogo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/public/media/OrdimintSVGLogo-black.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/public/media/OrdimintSVGLogo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/public/media/alby_icon_yellow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ordimint/web-app/07dca1cf27b68d110babfa625c1060a48571e91a/public/media/alby_icon_yellow.png
--------------------------------------------------------------------------------
/public/media/alby_icon_yellow.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/bitbox-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ordimint/web-app/07dca1cf27b68d110babfa625c1060a48571e91a/public/media/bitbox-logo.png
--------------------------------------------------------------------------------
/public/media/dorian-nakamoto.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ordimint/web-app/07dca1cf27b68d110babfa625c1060a48571e91a/public/media/dorian-nakamoto.jpg
--------------------------------------------------------------------------------
/public/media/glow1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/public/media/glow2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/public/media/green-check.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ordimint/web-app/07dca1cf27b68d110babfa625c1060a48571e91a/public/media/green-check.gif
--------------------------------------------------------------------------------
/public/media/ledger-logo-small.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
9 |
10 |
--------------------------------------------------------------------------------
/public/media/ledger-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/logo-dark.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ordimint/web-app/07dca1cf27b68d110babfa625c1060a48571e91a/public/media/logo-dark.jpeg
--------------------------------------------------------------------------------
/public/media/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ordimint/web-app/07dca1cf27b68d110babfa625c1060a48571e91a/public/media/logo.png
--------------------------------------------------------------------------------
/public/media/nos-ft-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ordimint/web-app/07dca1cf27b68d110babfa625c1060a48571e91a/public/media/nos-ft-logo.png
--------------------------------------------------------------------------------
/public/media/ordimint-coin-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ordimint/web-app/07dca1cf27b68d110babfa625c1060a48571e91a/public/media/ordimint-coin-white.png
--------------------------------------------------------------------------------
/public/media/ordimint-coin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ordimint/web-app/07dca1cf27b68d110babfa625c1060a48571e91a/public/media/ordimint-coin.png
--------------------------------------------------------------------------------
/public/media/text-placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ordimint/web-app/07dca1cf27b68d110babfa625c1060a48571e91a/public/media/text-placeholder.png
--------------------------------------------------------------------------------
/public/media/unisat-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
--------------------------------------------------------------------------------
/public/media/xverse-logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ordimint/web-app/07dca1cf27b68d110babfa625c1060a48571e91a/public/media/xverse-logo.jpg
--------------------------------------------------------------------------------
/public/media/xverse-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ordimint/web-app/07dca1cf27b68d110babfa625c1060a48571e91a/public/media/xverse-logo.png
--------------------------------------------------------------------------------
/public/noThumbnail.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | No Picture found
5 |
21 |
22 |
23 | No Picture found
24 |
25 |
--------------------------------------------------------------------------------
/public/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | https://ordimint.com/check-order
4 | hourly
5 |
6 |
7 | https://ordimint.com/faq
8 | hourly
9 |
10 |
11 | https://ordimint.com
12 | hourly
13 |
14 |
15 | https://ordimint.com/ordinal-collections
16 | hourly
17 |
18 |
19 | https://ordimint.com/search
20 | hourly
21 |
22 |
23 | https://ordimint.com/ordinal-collections/[slug]
24 | hourly
25 |
26 |
27 | https://ordimint.com/wallet/alby
28 | hourly
29 |
30 |
31 | https://ordimint.com/wallet/index
32 | hourly
33 |
34 |
35 | https://ordimint.com/wallet/ledger
36 | hourly
37 |
38 |
39 | https://ordimint.com/wallet/ordimint
40 | hourly
41 |
42 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const env = process.env.NODE_ENV || 'development';
2 |
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 | const CopyWebpackPlugin = require('copy-webpack-plugin');
5 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
6 | const autoprefixer = require('autoprefixer');
7 | const postcssPresets = require('postcss-preset-env');
8 | const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
9 |
10 | const finalCSSLoader = (env === 'production') ? MiniCssExtractPlugin.loader : { loader: 'style-loader' };
11 |
12 | module.exports = {
13 | mode: env,
14 | resolve: {
15 | alias: {
16 | 'ledgerhq/devices': 'ledgerhq/devices/lib-es'
17 | }
18 | },
19 | output: { publicPath: '/' },
20 | entry: ['./src'], // this is where our app lives
21 | devtool: 'source-map', // this enables debugging with source in chrome devtools
22 | devServer: {
23 | static: {
24 | directory: './dist',
25 | },
26 | port: 3000,
27 | open: true,
28 | compress: true,
29 | hot: true,
30 | historyApiFallback: true,
31 | },
32 | experiments: {
33 | asyncWebAssembly: true
34 | },
35 | module: {
36 | rules: [
37 | {
38 | test: /\.js$/,
39 | exclude: /node_modules/,
40 | use: [
41 | { loader: 'babel-loader' },
42 | ],
43 | },
44 | {
45 | test: /\.s?css/,
46 | use: [
47 | finalCSSLoader,
48 | {
49 | loader: 'css-loader',
50 | options: {
51 | sourceMap: true,
52 | },
53 | },
54 | {
55 | loader: 'postcss-loader',
56 | ident: 'postcss',
57 | options: {
58 | sourceMap: true,
59 | postcssOptions: {
60 | plugins: [
61 | autoprefixer(),
62 | postcssPresets({ browsers: 'last 2 versions' }),
63 | ],
64 | },
65 | },
66 | },
67 | {
68 | loader: 'sass-loader',
69 | options: {
70 | sourceMap: true,
71 | },
72 | },
73 | ],
74 | },
75 | {
76 | test: /\.(jpe?g|png|gif|svg)$/,
77 | use: [
78 | {
79 | loader: 'url-loader'
80 | }
81 | ]
82 | },
83 | ],
84 | },
85 | plugins: [
86 | new MiniCssExtractPlugin({
87 | filename: '[name].css',
88 | }),
89 | new NodePolyfillPlugin(),
90 | new HtmlWebpackPlugin({
91 | template: './public/index.html',
92 | filename: './index.html',
93 | favicon: './public/favicon.ico',
94 |
95 | }),
96 | new CopyWebpackPlugin({
97 | patterns: [
98 | { from: './src/media', to: './' },
99 | ]
100 | }),
101 | ],
102 | };
--------------------------------------------------------------------------------