bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
217 lines (216 loc) • 9.18 kB
JavaScript
import { PrivateKey } from "@bsv/sdk";
// useCreateListing Hook - Create marketplace listings
import { useMutation } from "@tanstack/react-query";
import { TokenType, createOrdListings, createOrdTokenListings, } from "js-1sat-ord";
import { useBitcoinAuth } from "../../../hooks/useBitcoinAuth.js";
import { broadcastTransaction } from "../../../lib/broadcast";
import { getWalletProps, } from "../../wallet/types/extended-user.js";
export function useCreateListing(options = {}) {
const { app: _app = "bigblocks", feeRate = 0.5, onSuccess, onError, } = options;
const { user } = useBitcoinAuth();
// Create ordinal/NFT listings
const createListingMutation = useMutation({
mutationFn: async (listings) => {
if (!user) {
throw {
code: "UNAUTHORIZED",
message: "User must be authenticated to create listings",
};
}
if (!listings.length) {
throw {
code: "INVALID_INPUT",
message: "At least one listing is required",
};
}
try {
// Get wallet properties with defaults
const walletProps = getWalletProps(user);
// Get user's private keys
const paymentPk = walletProps.paymentKey
? PrivateKey.fromWif(walletProps.paymentKey)
: undefined;
const ordPk = walletProps.ordinalKey
? PrivateKey.fromWif(walletProps.ordinalKey)
: undefined;
if (!ordPk) {
throw {
code: "MISSING_KEYS",
message: "Ordinal private key is required for listing",
};
}
// Get payment UTXOs for fees
const paymentUtxos = walletProps.utxos;
if (!paymentUtxos.length) {
throw {
code: "INSUFFICIENT_FUNDS",
message: "No UTXOs available for transaction fees",
};
}
const config = {
utxos: paymentUtxos,
listings,
paymentPk,
ordPk,
changeAddress: walletProps.fundingAddress,
satsPerKb: Math.floor(feeRate * 1000),
};
const { tx, spentOutpoints: _spentOutpoints } = await createOrdListings(config);
// Broadcast transaction
const broadcastResult = await broadcastTransaction(tx);
if (!broadcastResult.success) {
throw {
code: "BROADCAST_FAILED",
message: broadcastResult.error || "Failed to broadcast transaction",
};
}
const result = {
txid: broadcastResult.txid ?? "",
fee: tx.inputs.reduce((sum, input) => sum +
(input.sourceTransaction?.outputs[input.sourceOutputIndex]
?.satoshis || 0), 0) -
tx.outputs.reduce((sum, output) => sum + (output.satoshis || 0), 0),
size: tx.toBinary().length,
listings: listings.length,
};
return result;
}
catch (error) {
if (error.code) {
throw error;
}
throw {
code: "TRANSACTION_FAILED",
message: "Failed to create listing transaction",
details: error,
};
}
},
onSuccess: (data) => {
onSuccess?.({ success: true, txid: data.txid });
},
onError: (error) => {
console.error("Create listing failed:", error);
onError?.(error);
},
});
// Create token listings (BSV20/BSV21)
const createTokenListingMutation = useMutation({
mutationFn: async ({ listings, params }) => {
if (!user) {
throw {
code: "UNAUTHORIZED",
message: "User must be authenticated to create token listings",
};
}
if (!listings.length) {
throw {
code: "INVALID_INPUT",
message: "At least one token listing is required",
};
}
try {
// Get wallet properties with defaults
const walletProps = getWalletProps(user);
// Get user's private keys
const paymentPk = walletProps.paymentKey
? PrivateKey.fromWif(walletProps.paymentKey)
: undefined;
const ordPk = walletProps.ordinalKey
? PrivateKey.fromWif(walletProps.ordinalKey)
: undefined;
if (!ordPk) {
throw {
code: "MISSING_KEYS",
message: "Ordinal private key is required for token listing",
};
}
// Get payment UTXOs and token UTXOs
const paymentUtxos = walletProps.utxos;
const tokenUtxos = walletProps.tokenUtxos.filter((utxo) => utxo.id === params.tokenID);
if (!paymentUtxos.length) {
throw {
code: "INSUFFICIENT_FUNDS",
message: "No UTXOs available for transaction fees",
};
}
if (!tokenUtxos.length) {
throw {
code: "INSUFFICIENT_TOKENS",
message: "No tokens available for listing",
};
}
const config = {
utxos: paymentUtxos,
listings,
paymentPk,
ordPk,
changeAddress: walletProps.fundingAddress,
tokenChangeAddress: walletProps.ordinalAddress,
satsPerKb: Math.floor(feeRate * 1000),
protocol: params.protocol === "bsv20" ? TokenType.BSV20 : TokenType.BSV21,
tokenID: params.tokenID,
decimals: params.decimals,
inputTokens: tokenUtxos,
};
const { tx, spentOutpoints: _spentOutpoints2 } = await createOrdTokenListings(config);
// Broadcast transaction
const broadcastResult = await broadcastTransaction(tx);
if (!broadcastResult.success) {
throw {
code: "BROADCAST_FAILED",
message: broadcastResult.error || "Failed to broadcast transaction",
};
}
const result = {
txid: broadcastResult.txid || "",
fee: tx.inputs.reduce((sum, input) => sum +
(input.sourceTransaction?.outputs[input.sourceOutputIndex]
?.satoshis || 0), 0) -
tx.outputs.reduce((sum, output) => sum + (output.satoshis || 0), 0),
size: tx.toBinary().length,
listings: listings.length,
};
return result;
}
catch (error) {
if (error.code) {
throw error;
}
throw {
code: "TRANSACTION_FAILED",
message: "Failed to create token listing transaction",
details: error,
};
}
},
onSuccess: (data) => {
onSuccess?.({ success: true, txid: data.txid });
},
onError: (error) => {
console.error("Create token listing failed:", error);
onError?.(error);
},
});
return {
// Mutations
createListing: createListingMutation.mutate,
createListingAsync: createListingMutation.mutateAsync,
createTokenListing: (listings, params) => {
createTokenListingMutation.mutate({ listings, params });
},
createTokenListingAsync: (listings, params) => {
return createTokenListingMutation.mutateAsync({ listings, params });
},
// State
isLoading: createListingMutation.isPending || createTokenListingMutation.isPending,
isError: createListingMutation.isError || createTokenListingMutation.isError,
isSuccess: createListingMutation.isSuccess || createTokenListingMutation.isSuccess,
error: createListingMutation.error || createTokenListingMutation.error,
// Reset
reset: () => {
createListingMutation.reset();
createTokenListingMutation.reset();
},
};
}