Skip to content

Instantly share code, notes, and snippets.

@sherbakovdev
Created April 29, 2023 17:40
Show Gist options
  • Save sherbakovdev/b1cacd42517bcb64027cfea198f3eece to your computer and use it in GitHub Desktop.
Save sherbakovdev/b1cacd42517bcb64027cfea198f3eece to your computer and use it in GitHub Desktop.
useOrdinalSafe React hook. Connect to a React app to make inscriptions.
"use client";
import * as React from "react";
import { useOrdinalSafe } from "@/hooks/useOrdinalSafe";
type Props = {
data: string;
};
export const InscriptionButton = ({ data }: Props) => {
const wallet = useOrdinalSafe();
if (wallet.injection.isError) {
return <div>{wallet.injection.error}</div>;
}
if (wallet.initialization.isError) {
return <div>{wallet.initialization.error}</div>;
}
if (wallet.injection.isSuccess && wallet.initialization.isIdle) {
return <button onClick={wallet.initialize}>Connect wallet</button>;
}
return (
<>
<button
disabled={wallet.inscription.isLoading}
onClick={() => wallet.inscribe({ mime: "application/json", data })}
>
{wallet.inscription.isLoading ? "Inscribing..." : "Inscribe"}
</button>
{wallet.inscription.isError ? (
<div>{wallet.inscription.error}</div>
) : null}
{wallet.inscription.isSuccess ? (
<div>
<div>Commit: {wallet.inscription.data?.commit}</div>
<div>Reveal: {wallet.inscription.data?.reveal}</div>
</div>
) : null}
</>
);
};
import * as React from "react";
export const useOrdinalSafe = () => {
const OS = React.useRef<IWallet>();
const [injection, setInjection] = React.useState(state.initial);
const [inscription, setInscription] = React.useState<InscriptionResult>(
state.initial
);
const [initialization, setInitialization] = React.useState(state.initial);
React.useEffect(() => {
if (typeof window?.ordinalSafe === "undefined") {
return setInjection({
...state.error,
error: "OrdinalSafe is not found",
});
}
OS.current = window.ordinalSafe;
setInjection(state.success);
}, []);
const initialize = React.useCallback(() => {
OS.current?.requestAccounts().then(
() => setInitialization(state.success),
() =>
setInitialization({ ...state.error, error: "Initialization failed" })
);
}, []);
const inscribe = (params: { mime: Mime; data: string }) => {
setInscription(state.loading);
const dataInHex = Buffer.from(params.data).toString("hex");
OS.current?.inscribe(params.mime, dataInHex).then(
(data) =>
setInscription({
...state.success,
data,
}),
() =>
setInscription({
...state.error,
error: "Inscription failed",
})
);
};
return {
inscribe,
initialize,
injection,
inscription,
initialization,
wallet: OS.current,
};
};
const state = {
initial: {
error: "",
isIdle: true,
isError: false,
isSuccess: false,
isLoading: false,
},
success: {
error: "",
isIdle: false,
isError: false,
isSuccess: true,
isLoading: false,
},
error: {
error: "",
isIdle: false,
isError: true,
isSuccess: false,
isLoading: false,
},
loading: {
error: "",
isIdle: false,
isError: false,
isLoading: true,
isSuccess: false,
},
};
type InscriptionResult = typeof state.initial & {
data?: { commit: string; reveal: string };
};
type Mime =
| "image/jpeg"
| "image/png"
| "image/gif"
| "image/webp"
| "image/svg+xml"
| "application/json"
| "text/html;charset=utf-8"
| "text/plain;charset=utf-8";
interface IWallet {
isOrdinalSafe: boolean;
version: string;
/**
* Should be called before any other method in order to initialize the wallet.
* OrdinalSafe is taproot only.
* @returns accounts List of taproot addresses. The first one is the default account.
*/
requestAccounts(): Promise<string[]>;
/**
* Should be called to get signed psbt.
* Only signs the inputs that is owned by the wallet.
* Only send taproot inputs. Any other inputs that is not taproot will result in reject message.
* @param psbt Hex string of psbt
* @returns psbt Hex string of signed psbt
*/
signPsbt(psbt: string): Promise<string>;
/**
* Inscribes trustlessly on behalf of the wallet.
* @param mimeType Mime type of the data (Types of data that will be displayed:
* image/jpeg,
* image/png,
* image/gif',
* image/webp',
* image/svg+xml,
* application/json,
* text/html;charset=utf-8,
* text/plain;charset=utf-8)
* @param data Hex encoded data to inscribe
* @returns commit Commit transaction id
* @returns reveal Reveal transaction id
*/
inscribe(
mimeType: string,
data: string
): Promise<{ commit: string; reveal: string }>;
/**
* Broadcasts a transaction to the network.
* Just propagates to rpc.
* Does not wait for confirmation.
* Does not check if the transaction is valid.
* @param txHex Hex string of transaction
* @returns txHash Hash of the transaction
*/
broadcastTransaction(txHex: string): Promise<string>;
/**
* Sign a message with the wallet.
* Signs according to bip-322 (https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki).
* @param message Message to sign
* @returns signature Base64 encoded string of signature
*/
signMessage(message: string): Promise<string>;
/**
* Balance of the default account.
* @returns balance Balance in satoshis
*/
getBalance(): Promise<number>;
/**
* Inscriptions of the default account.
* @returns inscriptions List of inscriptionIds that are owned by the default account
*/
getInscriptions(): Promise<string[]>;
/**
* Returns confirmed UTXOs of the default account.
* Ordinal UTXOs are marked as frozen. They also contain extra information about the inscription.
* @param type "all" for all utxos, "cardinals" for cardinals only, "ordinals" for ordinals only
* @returns utxos List of UTXOs
*/
getUTXOs(type: string): Promise<UTXO[]>;
}
interface UTXO {
blockHash: string;
frozen: boolean;
index: number;
isSpent: boolean;
meta: {
addresses: string[];
index: number;
script: string;
scriptType: string;
};
status: string;
txId: string;
value: number;
inscriptions: Inscription[];
}
interface Inscription {
genesisFee: number;
genesisHeight: number;
inscriptionId: string;
number: number;
output: {
script_pubkey: string;
value: number;
};
sat: any;
satpoint: string;
timestamp: number;
}
declare global {
interface Window {
ordinalSafe: IWallet;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment