Skip to content

Commit

Permalink
Merge pull request #34 from lana-shanghai/li/feat/request_inscription…
Browse files Browse the repository at this point in the history
…_onchain

Text and small PNG image request works onchain, Xverse address fetched
  • Loading branch information
lana-shanghai authored Dec 31, 2024
2 parents 33a09c8 + a2cff4f commit 4bcb9b0
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 35 deletions.
57 changes: 42 additions & 15 deletions apps/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,24 @@ function App() {
setIsStarknetConnected(false)
}

// Bitcoin Wallet Connect
const [taprootAddress, setTaprootAddress] = useState<string | null>(null)

const connectBitcoinWalletHandler = async () => {
const addresses = await connectBitcoinWallet()
setBitcoinWallet(addresses)
// TODO: replace with the Ordinals address.
// Currently the sats connect lib fetches
// only the Payments address from Xverse.
if (addresses.paymentAddress) {
setTaprootAddress(addresses.paymentAddress)
setBitcoinWallet((prev) => ({
...prev,
paymentAddress: addresses.paymentAddress,
}))
} else {
console.error('Ordinals address not found in wallet connection')
}
}

// Bitcoin Wallet Disconnect

const disconnectBitcoinWallet = () => {
setBitcoinWallet({ paymentAddress: null, ordinalsAddress: null, stacksAddress: null })
}
Expand All @@ -113,21 +124,23 @@ function App() {
});

const [calls, setCalls] = useState([] as any[])
const requestInscriptionCall = async () => {

const requestInscriptionCall = async (dataToInscribe: string, taprootAddress: string) => {
if (!address || !orderbookContract) {
return
}

const calldata = CallData.compile([
byteArray.byteArrayFromString("message:Hello, Starknet!"),
byteArray.byteArrayFromString("tb1234567890123456789012345678901234567890"),
Number(100),
byteArray.byteArrayFromString(dataToInscribe),
byteArray.byteArrayFromString(taprootAddress),
Number(100), // TODO remove when contract is re-deployed
toHex("STRK"),
uint256.bnToUint256(2000)
]);
setCalls(
[orderbookContract.populate('request_inscription', calldata)]
)
uint256.bnToUint256(2000),
])

setCalls([await orderbookContract.populate('request_inscription', calldata)])
}

const { send, data, isPending } = useSendTransaction({
calls
});
Expand Down Expand Up @@ -174,14 +187,28 @@ function App() {
connectWallet: connectBitcoinWalletHandler,
disconnectWallet: disconnectBitcoinWallet
}}
/> <div className="h-[4.5rem]" />
/>
<div className="h-[4.5rem]" />
<Routes>
{tabs.map((tab) => (
<Route key={tab.path} path={tab.path} element={<tab.component {...tabProps} />} />
<Route
key={tab.path}
path={tab.path}
element={
<tab.component
taprootAddress={taprootAddress}
connectBitcoinWalletHandler={connectBitcoinWalletHandler}
disconnectBitcoinWallet={disconnectBitcoinWallet}
isBitcoinWalletConnected={!!taprootAddress}
{...tabProps}
/>
}
/>
))}
<Route path="/inscription/:id" element={<Inscription {...tabProps} />} />
<Route path="/request/:id" element={<Request {...tabProps} />} />
</Routes>

</div>
)
}
Expand Down
90 changes: 75 additions & 15 deletions apps/web/src/components/inscription/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,44 @@ function InscriptionForm(props: any) {

const handleSubmit = async (e: any) => {
e.preventDefault();
if (!uploadedImage) {
setErrorMessage("Please upload an image");

let dataToInscribe = "";

if (selectedOption === "Image") {
if (!uploadedImage) {
setErrorMessage("Please upload an image");
return;
}
const response = await fetch(uploadedImage);
const blob = await response.blob();
const base64Image = await new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result as string);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
dataToInscribe = base64Image;
} else if (selectedOption === "Message") {
const textAreaElement = document.querySelector<HTMLTextAreaElement>(".Form__textarea");
dataToInscribe = textAreaElement?.value || "";
if (!dataToInscribe) {
setErrorMessage("Please enter a message to inscribe");
return;
}
}

const taprootAddress = props.taprootAddress;
if (!taprootAddress) {
setErrorMessage("Taproot address not found. Please connect Bitcoin wallet.");
return;
}
await props.requestInscriptionCall();

setErrorMessage("");

await props.requestInscriptionCall(dataToInscribe, taprootAddress);
props.setIsInscribing(true);
};

}
const handleImageUpload = (e: any) => {
e.preventDefault();
if (e.target.files && e.target.files[0]) {
Expand All @@ -39,28 +69,58 @@ function InscriptionForm(props: any) {
<div className="flex-grow Form__input">
{selectedOption === "Image" ? (
<div>
<label className={`text-lg Form__image ${uploadedImage ? "Form__image--grid" : ""}`} htmlFor="image" onDrop={handleImageUpload} onDragOver={handleImgDrag}>
{uploadedImage ? "Image to Inscribe →" : "Upload an Image..."} {uploadedImage && <img className="Form__image__up" src={uploadedImage} alt="uploaded" />}
<label
className={`text-lg Form__image ${uploadedImage ? "Form__image--grid" : ""}`}
htmlFor="image"
onDrop={handleImageUpload}
onDragOver={handleImgDrag}
>
{uploadedImage ? "Image to Inscribe →" : "Upload an Image..."}{" "}
{uploadedImage && <img className="Form__image__up" src={uploadedImage} alt="uploaded" />}
</label>
<input style={{display: 'none'}} type="file" name="image" id="image" accept="image/*" onChange={handleImageUpload}/>
<input
style={{ display: "none" }}
type="file"
name="image"
id="image"
accept="image/*"
onChange={handleImageUpload}
/>
</div>
) : (
<textarea className="text-lg Form__textarea" placeholder="Enter a message to inscribe..." />
<textarea
className="text-lg Form__textarea"
placeholder="Enter a message to inscribe..."
/>
)}
</div>
<div className="flex flex-row items-center justify-center relative">
<DropButton options={dropOptions} selectedOption={selectedOption} setSelectedOption={setSelectedOption} />
<button type="submit" className={`button--gradient button__primary ml-4 ${uploadedImage ? "button__primary--pinging": "button__primary--disabled"}`}>
Inscribe
<DropButton
options={dropOptions}
selectedOption={selectedOption}
setSelectedOption={setSelectedOption}
/>
<button
type="submit"
disabled={
(selectedOption === "Image" && !uploadedImage) || !props.taprootAddress
}
className={`button--gradient button__primary ml-4 ${
(selectedOption === "Image" && uploadedImage) || selectedOption === "Message"
? "button__primary--pinging"
: "button__primary--disabled"
}`}
>
Request Inscription
</button>
{errorMessage &&
{errorMessage && (
<p className="absolute right-0 translate-x-[110%] text-red-500 text-xs">
{errorMessage}
</p>
}
)}
</div>
</form>
);
);
}

export default InscriptionForm;
32 changes: 27 additions & 5 deletions apps/web/src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,25 @@ import InscriptionStatus from "../components/inscription/Status";
import { Pagination } from "../components/Pagination";
import { getNewInscriptions } from "../api/inscriptions";

function Home(props: any) {
function Home(props: {
requestInscriptionCall: (dataToInscribe: string, taprootAddress: string) => Promise<void>;
taprootAddress: string | null;
connectBitcoinWalletHandler: () => Promise<void>;
disconnectBitcoinWallet: () => void;
isBitcoinWalletConnected: boolean;
}) {
const [isInscribing, setIsInscribing] = useState(false);

const defaultInscription: any[] = [];
const [recentInscriptions, setRecentInscriptions] = useState(defaultInscription);
const [recentsPagination, setRecentsPagination] = useState({
pageLength: 16,
page: 1
page: 1,
});

useEffect(() => {
const fetchInscriptions = async () => {
// TODO fetch real new inscriptions from smart contract
let result = await getNewInscriptions(recentsPagination.pageLength, recentsPagination.page);
if (result.data) {
if (recentsPagination.page === 1) {
Expand All @@ -29,20 +36,35 @@ function Home(props: any) {
setRecentInscriptions([...recentInscriptions, ...newInscriptions]);
}
}
}
};
try {
fetchInscriptions();
} catch (error) {
console.error(error);
}
}, [recentsPagination]);
}, [recentsPagination, isInscribing]);

return (
<div className="w-full flex flex-col h-max">
<div className="bg__color--tertiary w-full flex flex-col items-center justify-center py-8">
<h1 className="text-4xl font-bold">Inscribe on Bitcoin</h1>
<h2 className="text-lg mb-8">Starknet's Decentralized Inscriptor Network</h2>
<InscriptionForm isInscribing={isInscribing} setIsInscribing={setIsInscribing} requestInscriptionCall={props.requestInscriptionCall} />
{!props.isBitcoinWalletConnected && (
<button
className="button--gradient button__primary"
onClick={props.connectBitcoinWalletHandler}
>
Connect Bitcoin Wallet
</button>
)}
{props.isBitcoinWalletConnected && (
<InscriptionForm
isInscribing={isInscribing}
setIsInscribing={setIsInscribing}
requestInscriptionCall={props.requestInscriptionCall}
taprootAddress={props.taprootAddress}
/>
)}
{isInscribing && <InscriptionStatus />}
</div>
<div className="w-full flex flex-col items-center py-2 bg__color--primary h-full border-t-2 border-[var(--color-primary-light)]">
Expand Down

0 comments on commit 4bcb9b0

Please sign in to comment.