UNPKG

bigblocks

Version:

Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React

17 lines 12.7 kB
{ "name": "inscriptions-components-bsv20mintbutton", "type": "registry:component", "dependencies": [ "js-1sat-ord" ], "devDependencies": [], "registryDependencies": [], "files": [ { "path": "components/inscriptions/components/BSV20MintButton.tsx", "type": "registry:component", "content": "/**\n * BSV20MintButton Component\n *\n * Component for deploying and minting BSV-20 fungible tokens\n * Supports both token deployment and minting operations\n */\n\nimport {\n\tCircleBackslashIcon,\n\tInfoCircledIcon,\n\tPlusIcon,\n} from \"@radix-ui/react-icons\";\nimport type { PreMAP } from \"js-1sat-ord\";\nimport { useCallback, useMemo, useState } from \"react\";\nimport { Badge } from \"../../ui/badge.js\";\nimport { Input } from \"../../ui/input.js\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"../../ui/select.js\";\nimport { Separator } from \"../../ui/separator.js\";\nimport { InscriptionButton } from \"./InscriptionButton.js\";\n\nexport interface BSV20Token {\n\t/** Token ticker symbol */\n\tticker: string;\n\t/** Total supply (for deploy) or amount to mint */\n\tamount: string;\n\t/** Number of decimal places */\n\tdecimals?: number;\n\t/** Maximum supply limit (for deploy) */\n\tlimit?: string;\n}\n\nexport interface BSV20MintButtonProps {\n\t/** Operation type */\n\toperation?: \"deploy\" | \"mint\" | \"both\";\n\t/** Pre-filled token data */\n\ttoken?: Partial<BSV20Token>;\n\t/** Button text */\n\tbuttonText?: string;\n\t/** Dialog title */\n\tdialogTitle?: string;\n\t/** Button variant */\n\tvariant?: \"solid\" | \"soft\" | \"outline\" | \"ghost\";\n\t/** Button size */\n\tsize?: \"1\" | \"2\" | \"3\" | \"4\";\n\t/** Button color */\n\tcolor?: \"blue\" | \"green\" | \"red\" | \"gray\";\n\t/** Additional CSS classes */\n\tclassName?: string;\n\t/** Whether button is disabled */\n\tdisabled?: boolean;\n\t/** Show fee estimate */\n\tshowFeeEstimate?: boolean;\n\t/** Number of iterations for bulk minting */\n\titerations?: number;\n\t/** Success callback */\n\tonSuccess?: (txid: string, token: BSV20Token) => void;\n\t/** Error callback */\n\tonError?: (error: Error) => void;\n}\n\nexport function BSV20MintButton({\n\toperation = \"both\",\n\ttoken: initialToken,\n\tbuttonText = \"BSV-20 Token\",\n\tdialogTitle,\n\tvariant = \"solid\",\n\tsize = \"2\",\n\tcolor = \"blue\",\n\tclassName = \"\",\n\tdisabled = false,\n\tshowFeeEstimate = true,\n\titerations = 1,\n\tonSuccess,\n\tonError,\n}: BSV20MintButtonProps) {\n\tconst [selectedOperation, setSelectedOperation] = useState<\"deploy\" | \"mint\">(\n\t\toperation === \"both\" ? \"deploy\" : operation,\n\t);\n\n\t// Form state\n\tconst [ticker, setTicker] = useState(initialToken?.ticker || \"\");\n\tconst [amount, setAmount] = useState(initialToken?.amount || \"\");\n\tconst [decimals, setDecimals] = useState(\n\t\tinitialToken?.decimals?.toString() || \"8\",\n\t);\n\tconst [limit, setLimit] = useState(initialToken?.limit || \"\");\n\tconst [mintIterations, setMintIterations] = useState(iterations.toString());\n\n\t// Validation\n\tconst [errors, setErrors] = useState<Record<string, string>>({});\n\n\t// Generate inscription content\n\tconst generateContent = useCallback((): string => {\n\t\tconst data: Record<string, string | number> = {\n\t\t\tp: \"bsv-20\",\n\t\t\top: selectedOperation,\n\t\t\ttick: ticker.toUpperCase(),\n\t\t};\n\n\t\tif (selectedOperation === \"deploy\") {\n\t\t\tdata.max = limit || amount;\n\t\t\tdata.lim = limit || amount;\n\t\t\tif (decimals && decimals !== \"0\") {\n\t\t\t\tdata.dec = decimals;\n\t\t\t}\n\t\t} else {\n\t\t\tdata.amt = amount;\n\t\t}\n\n\t\treturn JSON.stringify(data);\n\t}, [selectedOperation, ticker, amount, decimals, limit]);\n\n\t// Generate metadata\n\tconst generateMetadata = useCallback((): PreMAP => {\n\t\treturn {\n\t\t\tapp: \"bigblocks\",\n\t\t\ttype: \"token\",\n\t\t\tsubType: \"bsv20\",\n\t\t\toperation: selectedOperation,\n\t\t\tticker: ticker.toUpperCase(),\n\t\t\t...(selectedOperation === \"deploy\" && {\n\t\t\t\ttotalSupply: limit || amount,\n\t\t\t\tdecimals: Number.parseInt(decimals) || 0,\n\t\t\t}),\n\t\t\t...(selectedOperation === \"mint\" && {\n\t\t\t\tamount,\n\t\t\t\titerations: Number.parseInt(mintIterations) || 1,\n\t\t\t}),\n\t\t};\n\t}, [selectedOperation, ticker, amount, decimals, limit, mintIterations]);\n\n\t// Validate form\n\tconst validateForm = useCallback((): boolean => {\n\t\tconst newErrors: Record<string, string> = {};\n\n\t\tif (!ticker || ticker.length < 1 || ticker.length > 5) {\n\t\t\tnewErrors.ticker = \"Ticker must be 1-5 characters\";\n\t\t}\n\n\t\tif (!amount || Number.parseFloat(amount) <= 0) {\n\t\t\tnewErrors.amount = \"Amount must be greater than 0\";\n\t\t}\n\n\t\tif (selectedOperation === \"deploy\") {\n\t\t\tif (\n\t\t\t\tdecimals &&\n\t\t\t\t(Number.parseInt(decimals) < 0 || Number.parseInt(decimals) > 18)\n\t\t\t) {\n\t\t\t\tnewErrors.decimals = \"Decimals must be 0-18\";\n\t\t\t}\n\t\t}\n\n\t\tif (selectedOperation === \"mint\" && mintIterations) {\n\t\t\tconst iter = Number.parseInt(mintIterations);\n\t\t\tif (iter < 1 || iter > 1000) {\n\t\t\t\tnewErrors.iterations = \"Iterations must be 1-1000\";\n\t\t\t}\n\t\t}\n\n\t\tsetErrors(newErrors);\n\t\treturn Object.keys(newErrors).length === 0;\n\t}, [ticker, amount, decimals, selectedOperation, mintIterations]);\n\n\t// Handle success\n\tconst handleSuccess = useCallback(\n\t\t(txid: string) => {\n\t\t\tconst tokenData: BSV20Token = {\n\t\t\t\tticker: ticker.toUpperCase(),\n\t\t\t\tamount,\n\t\t\t\tdecimals: Number.parseInt(decimals) || 0,\n\t\t\t\tlimit: limit || amount,\n\t\t\t};\n\t\t\tonSuccess?.(txid, tokenData);\n\t\t},\n\t\t[ticker, amount, decimals, limit, onSuccess],\n\t);\n\n\t// Calculate title\n\tconst title =\n\t\tdialogTitle ||\n\t\t(selectedOperation === \"deploy\"\n\t\t\t? \"Deploy BSV-20 Token\"\n\t\t\t: \"Mint BSV-20 Token\");\n\n\t// Format display values\n\tconst formattedAmount = useMemo(() => {\n\t\tif (!amount) return \"\";\n\t\tconst dec = Number.parseInt(decimals) || 0;\n\t\tconst num = Number.parseFloat(amount);\n\t\treturn num.toLocaleString(\"en-US\", {\n\t\t\tminimumFractionDigits: 0,\n\t\t\tmaximumFractionDigits: dec,\n\t\t});\n\t}, [amount, decimals]);\n\n\treturn (\n\t\t<InscriptionButton\n\t\t\tbuttonText={buttonText}\n\t\t\tdialogTitle={title}\n\t\t\tvariant={variant}\n\t\t\tsize={size}\n\t\t\tcolor={color}\n\t\t\tclassName={className}\n\t\t\tdisabled={disabled}\n\t\t\ticon={\n\t\t\t\tselectedOperation === \"deploy\" ? <PlusIcon /> : <CircleBackslashIcon />\n\t\t\t}\n\t\t\tcontent={validateForm() ? generateContent() : undefined}\n\t\t\tcontentType=\"application/json\"\n\t\t\tmetadata={validateForm() ? generateMetadata() : undefined}\n\t\t\titerations={\n\t\t\t\tselectedOperation === \"mint\" ? Number.parseInt(mintIterations) || 1 : 1\n\t\t\t}\n\t\t\tshowFeeEstimate={showFeeEstimate}\n\t\t\tonSuccess={handleSuccess}\n\t\t\tonError={onError}\n\t\t>\n\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t{/* Operation selector */}\n\t\t\t\t{operation === \"both\" && (\n\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t<p className=\"text-sm font-medium\">Operation</p>\n\t\t\t\t\t\t<Select\n\t\t\t\t\t\t\tvalue={selectedOperation}\n\t\t\t\t\t\t\tonValueChange={(v) =>\n\t\t\t\t\t\t\t\tsetSelectedOperation(v as \"deploy\" | \"mint\")\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<SelectTrigger>\n\t\t\t\t\t\t\t\t<SelectValue />\n\t\t\t\t\t\t\t</SelectTrigger>\n\t\t\t\t\t\t\t<SelectContent>\n\t\t\t\t\t\t\t\t<SelectItem value=\"deploy\">Deploy New Token</SelectItem>\n\t\t\t\t\t\t\t\t<SelectItem value=\"mint\">Mint Existing Token</SelectItem>\n\t\t\t\t\t\t\t</SelectContent>\n\t\t\t\t\t\t</Select>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\n\t\t\t\t<Separator />\n\n\t\t\t\t{/* Token ticker */}\n\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t<p className=\"text-sm font-medium\">Token Ticker</p>\n\t\t\t\t\t<Input\n\t\t\t\t\t\tplaceholder=\"e.g., PEPE\"\n\t\t\t\t\t\tvalue={ticker}\n\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\tsetTicker(e.target.value.toUpperCase().slice(0, 5))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstyle={{ textTransform: \"uppercase\" }}\n\t\t\t\t\t/>\n\t\t\t\t\t{errors.ticker && (\n\t\t\t\t\t\t<p className=\"text-sm text-destructive\">{errors.ticker}</p>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\n\t\t\t\t{/* Amount/Supply */}\n\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t<p className=\"text-sm font-medium\">\n\t\t\t\t\t\t{selectedOperation === \"deploy\" ? \"Total Supply\" : \"Mint Amount\"}\n\t\t\t\t\t</p>\n\t\t\t\t\t<Input\n\t\t\t\t\t\tplaceholder=\"e.g., 1000000\"\n\t\t\t\t\t\tvalue={amount}\n\t\t\t\t\t\tonChange={(e) => setAmount(e.target.value)}\n\t\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t\tmin=\"0\"\n\t\t\t\t\t\tstep=\"any\"\n\t\t\t\t\t/>\n\t\t\t\t\t{errors.amount && (\n\t\t\t\t\t\t<p className=\"text-sm text-destructive\">{errors.amount}</p>\n\t\t\t\t\t)}\n\t\t\t\t\t{formattedAmount && (\n\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\t{formattedAmount} {ticker || \"tokens\"}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\n\t\t\t\t{/* Deploy-specific fields */}\n\t\t\t\t{selectedOperation === \"deploy\" && (\n\t\t\t\t\t<>\n\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t<p className=\"text-sm font-medium\">Decimals</p>\n\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\tplaceholder=\"8\"\n\t\t\t\t\t\t\t\tvalue={decimals}\n\t\t\t\t\t\t\t\tonChange={(e) => setDecimals(e.target.value)}\n\t\t\t\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t\t\t\tmin=\"0\"\n\t\t\t\t\t\t\t\tmax=\"18\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t{errors.decimals && (\n\t\t\t\t\t\t\t\t<p className=\"text-sm text-destructive\">{errors.decimals}</p>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\tNumber of decimal places (0-18)\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t<p className=\"text-sm font-medium\">Mint Limit (Optional)</p>\n\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\tplaceholder=\"Same as total supply\"\n\t\t\t\t\t\t\t\tvalue={limit}\n\t\t\t\t\t\t\t\tonChange={(e) => setLimit(e.target.value)}\n\t\t\t\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t\t\t\tmin=\"0\"\n\t\t\t\t\t\t\t\tstep=\"any\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\tMaximum amount per mint transaction\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</>\n\t\t\t\t)}\n\n\t\t\t\t{/* Mint-specific fields */}\n\t\t\t\t{selectedOperation === \"mint\" && (\n\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t<p className=\"text-sm font-medium\">Iterations</p>\n\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\tplaceholder=\"1\"\n\t\t\t\t\t\t\tvalue={mintIterations}\n\t\t\t\t\t\t\tonChange={(e) => setMintIterations(e.target.value)}\n\t\t\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t\t\tmin=\"1\"\n\t\t\t\t\t\t\tmax=\"1000\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t{errors.iterations && (\n\t\t\t\t\t\t\t<p className=\"text-sm text-destructive\">{errors.iterations}</p>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\tNumber of mint inscriptions to create (1-1000)\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t{Number.parseInt(mintIterations) > 1 && (\n\t\t\t\t\t\t\t<Badge className=\"bg-purple-100 text-purple-800\">\n\t\t\t\t\t\t\t\tTotal:{\" \"}\n\t\t\t\t\t\t\t\t{(\n\t\t\t\t\t\t\t\t\tNumber.parseFloat(amount || \"0\") *\n\t\t\t\t\t\t\t\t\tNumber.parseInt(mintIterations)\n\t\t\t\t\t\t\t\t).toLocaleString()}{\" \"}\n\t\t\t\t\t\t\t\t{ticker}\n\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\n\t\t\t\t<Separator />\n\n\t\t\t\t{/* Info box */}\n\t\t\t\t<div className=\"flex items-start gap-2 p-3 bg-blue-50 border border-blue-200 rounded-md\">\n\t\t\t\t\t<InfoCircledIcon style={{ marginTop: 2, flexShrink: 0 }} />\n\t\t\t\t\t<div className=\"flex flex-col gap-1\">\n\t\t\t\t\t\t<p className=\"text-sm font-medium\">\n\t\t\t\t\t\t\t{selectedOperation === \"deploy\"\n\t\t\t\t\t\t\t\t? \"Token Deployment\"\n\t\t\t\t\t\t\t\t: \"Token Minting\"}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t\t{selectedOperation === \"deploy\"\n\t\t\t\t\t\t\t\t? \"This will create a new BSV-20 token on the blockchain. The token ticker must be unique.\"\n\t\t\t\t\t\t\t\t: \"This will mint additional tokens for an existing BSV-20 token. Make sure the token exists and allows minting.\"}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</InscriptionButton>\n\t);\n}\n", "target": "<%- config.aliases.components %>/bsv20mintbutton.tsx" } ] }