ordinalsbot
Version:
Node.js library for OrdinalsBot API
407 lines (378 loc) • 14.5 kB
text/typescript
import * as bitcoin from "bitcoinjs-lib";
import { ClientOptions, InscriptionEnv, InscriptionEnvNetwork } from "../types";
import { LaunchpadClient } from "./client";
import {
BitcoinNetworkType,
SignTransactionPayload,
SignTransactionResponse,
signTransaction,
} from "sats-connect";
import {
ConfirmPaddingOutputsRequest,
ConfirmPaddingOutputsResponse,
CreateLaunchpadOfferRequest,
CreateLaunchpadOfferResponse,
CreateLaunchpadRequest,
CreateLaunchpadResponse,
GetAllocationRequest,
GetAllocationResponse,
GetListingRequest,
GetListingResponse,
LaunchpadMarketplaceCreateRequest,
LaunchpadMarketplaceCreateResponse,
SetupPaddingOutputsRequest,
SetupPaddingOutputsResponse,
SubmitLaunchpadOfferRequest,
SubmitLaunchpadOfferResponse,
GetLaunchpadStatusRequest,
GetLaunchpadStatusResponse,
SaveLaunchpadRequest,
SaveLaunchpadResponse,
SatsConnectWrapperResponse,
} from "../types/launchpad_types";
import { WALLET_PROVIDER } from "../types/marketplace_types";
/**
* A class for interacting with the Launchpad API to create marketplaces.
*/
export class Launchpad {
/**
* The Bitcoin network type to use ('Mainnet' or 'Testnet').
*/
private network: BitcoinNetworkType;
/**
* The instance of LaunchpadClient used to make API requests.
*/
private launchpadClientInstance!: LaunchpadClient;
/**
* Creates an instance of Launchpad.
* @param key The API key (optional).
* @param {InscriptionEnv} [environment='mainnet'] - The environment (e.g., "testnet" , "mainnet", "signet") (optional, defaults to mainnet).
* @param {ClientOptions} [options] - Options for enabling L402 support.
*/
constructor(key: string = "", environment: InscriptionEnv = InscriptionEnvNetwork.mainnet, options?: ClientOptions) {
if (this.launchpadClientInstance !== undefined) {
console.error("launchpad constructor was called multiple times");
return;
}
environment = InscriptionEnvNetwork[environment]??InscriptionEnvNetwork.mainnet;
switch (environment) {
case InscriptionEnvNetwork.mainnet:
this.network = BitcoinNetworkType.Mainnet;
break;
case InscriptionEnvNetwork.testnet:
this.network = BitcoinNetworkType.Testnet;
break;
case InscriptionEnvNetwork.signet:
// @ts-ignore
this.network = BitcoinNetworkType.Signet;
break;
default:
this.network = BitcoinNetworkType.Mainnet
break;
}
this.launchpadClientInstance = new LaunchpadClient(key, environment, options);
}
/**
* Creates a new marketplace using the Launchpad API.
* @param {LaunchpadMarketplaceCreateRequest} createMarketplaceRequest The request body for creating a new marketplace.
* @returns {Promise<LaunchpadMarketplaceCreateResponse>} A promise that resolves to the response from the API.
*/
createMarketPlace(
createMarketplaceRequest: LaunchpadMarketplaceCreateRequest
): Promise<LaunchpadMarketplaceCreateResponse> {
return this.launchpadClientInstance.createMarketPlace(
createMarketplaceRequest
);
}
/**
* Creates a new launchpad using the Launchpad API.
* @param {CreateLaunchpadRequest} createLaunchpadRequest The request body for creating a new marketplace.
* @returns {Promise<CreateLaunchpadResponse | SaveLaunchpadResponse>} A promise that resolves to the response from the API.
*/
async createLaunchpad(
createLaunchpadRequest: CreateLaunchpadRequest
): Promise<CreateLaunchpadResponse | SaveLaunchpadResponse> {
try {
if (!createLaunchpadRequest.walletProvider) {
return await this.launchpadClientInstance.createLaunchpad(
createLaunchpadRequest
);
} else if (
createLaunchpadRequest.walletProvider === WALLET_PROVIDER.xverse
) {
// Create the payload for signing the seller transaction
if (!createLaunchpadRequest.sellerOrdinalAddress) {
throw new Error("No seller address provided");
}
// Creates a new launchpad
const { launchpadId, status } =
await this.launchpadClientInstance.createLaunchpad(
createLaunchpadRequest
);
let inputIndices: any = [];
let index = 0;
//input indices for the all the ordinals in the phase
createLaunchpadRequest.phases.forEach((phase) => {
phase.ordinals.forEach((ordinal) => {
inputIndices.push(index);
index++;
});
});
// seller input for sign the tractions
const sellerInput = {
address: createLaunchpadRequest.sellerOrdinalAddress,
signingIndexes: inputIndices,
sigHash:
bitcoin.Transaction.SIGHASH_SINGLE |
bitcoin.Transaction.SIGHASH_ANYONECANPAY,
};
const getLaunchpadStatusRequest = { launchpadId, status };
// waiting for launchpad psbt
const { psbt }: GetLaunchpadStatusResponse =
await this.getLaunchpadPSBT(getLaunchpadStatusRequest);
// transaction request payload
const payload = {
network: { type: this.network },
message: "Sign Seller Transaction",
psbtBase64: psbt,
broadcast: false,
inputsToSign: [sellerInput],
};
return new Promise(async (resolve, reject) => {
const response: SatsConnectWrapperResponse =
await this.satsConnectWrapper(payload);
if (response && response.success && response.psbtBase64) {
try {
// contruct the request payload for save the launchpad after signing the transaction
const saveLaunchpadRequestPayload: SaveLaunchpadRequest = {
launchpadId: launchpadId,
updateLaunchData: {
signedListingPSBT: response.psbtBase64,
},
};
resolve(this.saveLaunchpad(saveLaunchpadRequestPayload));
} catch (error) {
console.error("Error saving launchpad:", error);
reject(error);
}
} else {
console.log("Transaction canceled");
}
});
} else {
throw new Error("Wallet not supported");
}
} catch (error) {
console.error("Error in create Launchpad:", error);
throw error;
}
}
/**
* Fetch status of the launchpad using the Launchpad API.
* @param {GetLaunchpadStatusRequest} getLaunchpadStatusRequest The request body for get launchpad status.
* @returns {Promise<GetLaunchpadStatusResponse>} A promise that resolves to the response from the API.
*/
async getLaunchpadPSBT(
getLaunchpadStatusRequest: GetLaunchpadStatusRequest
): Promise<GetLaunchpadStatusResponse> {
let psbt = "";
let status: any;
let intervalId: any;
const pollPSBTStatus = async () => {
return new Promise((resolve) => {
const checkAndResolve = async () => {
const response: GetLaunchpadStatusResponse =
await this.launchpadClientInstance.getLaunchpadStatus(
getLaunchpadStatusRequest
);
if (response.status !== status && response.psbt) {
psbt = response.psbt;
status = response.status;
clearInterval(intervalId);
resolve(psbt);
}
};
intervalId = setInterval(checkAndResolve, 10000);
checkAndResolve();
setTimeout(() => {
clearInterval(intervalId);
resolve(psbt);
}, 5 * 60 * 1000);
});
};
await pollPSBTStatus();
return { psbt, status };
}
/**
* Updated the signed psbt by the seller on the launchpad
* @param {SaveLaunchpadRequest} saveLaunchpadRequest - The request body to update the launchpad data.
* @returns {Promise<SaveLaunchpadResponse>} A promise that resolves to the response from the API.
*/
saveLaunchpad(
saveLaunchpadRequest: SaveLaunchpadRequest
): Promise<SaveLaunchpadResponse> {
return this.launchpadClientInstance.saveLaunchpad(saveLaunchpadRequest);
}
/**
* Get all the launchpad listing
* @param {GetListingRequest} getListingRequest - The request object for get all launchpad.
* @returns {Promise<GetListingResponse>} A promise that resolves to the response from the API.
*/
getLaunchpadListing(
getListingRequest: GetListingRequest
): Promise<GetListingResponse> {
return this.launchpadClientInstance.getLaunchpadListing(getListingRequest);
}
/**
* Get buyer launhcpad allocation
* @param {GetAllocationRequest} getAllocationRequest - The request object for buyer launhcpad allocations.
* @returns {Promise<GetListingResponse>} A promise that resolves to the response from the API.
*/
getAllocation(
getAllocationRequest: GetAllocationRequest
): Promise<GetAllocationResponse> {
return this.launchpadClientInstance.getAllocation(getAllocationRequest);
}
/**
* Confirms Padding Outputs
* @param {ConfirmPaddingOutputsRequest} confirmPaddingOutputsRequest - The request object for confirms padding outputs
* @returns {Promise<ConfirmPaddingOutputsResponse>} A promise that resolves to the response from the API.
*/
confirmPaddingOutputs(
confirmPaddingOutputsRequest: ConfirmPaddingOutputsRequest
): Promise<ConfirmPaddingOutputsResponse> {
return this.launchpadClientInstance.confirmPaddingOutputs(
confirmPaddingOutputsRequest
);
}
/**
* Setup the padding output
* @param {SetupPaddingOutputsRequest} setupPaddingOutputsRequest - The request object for buyer setup padding outputs.
* @returns {Promise<SetupPaddingOutputsResponse>} A promise that resolves to the response from the API.
*/
async setupPaddingOutputs(
setupPaddingOutputsRequest: SetupPaddingOutputsRequest
): Promise<SetupPaddingOutputsResponse | SignTransactionResponse> {
if (!setupPaddingOutputsRequest.walletProvider) {
return this.launchpadClientInstance.setupPaddingOutputs(
setupPaddingOutputsRequest
);
} else if (
setupPaddingOutputsRequest.walletProvider === WALLET_PROVIDER.xverse
) {
const paddingOutputResponse: SetupPaddingOutputsResponse =
await this.launchpadClientInstance.setupPaddingOutputs(
setupPaddingOutputsRequest
);
const buyerInputs = {
address: setupPaddingOutputsRequest.address,
signingIndexes: paddingOutputResponse.buyerInputIndices,
};
const payload = {
network: {
type: this.network,
},
message: "Sign Padding Outputs Transaction",
psbtBase64: paddingOutputResponse.psbt,
broadcast: true,
inputsToSign: [buyerInputs],
};
return new Promise(async (resolve, reject) => {
const response: SatsConnectWrapperResponse =
await this.satsConnectWrapper(payload);
if (response && response.success && response.psbtBase64) {
resolve({ psbtBase64: response.psbtBase64, txId: response.txId });
} else {
console.log("Transaction canceled");
}
});
} else {
throw new Error("Wallet not supported");
}
}
/**
* Creates the launchpad offer
* @param {CreateLaunchpadOfferRequest} createOfferRequest - The request object for create Launchpad Offer.
* @returns {Promise<CreateLaunchpadOfferResponse>} A promise that resolves to the response from the API.
*/
async createLaunchpadOffer(
createOfferRequest: CreateLaunchpadOfferRequest
): Promise<CreateLaunchpadOfferResponse | SubmitLaunchpadOfferRequest> {
if (!createOfferRequest.walletProvider) {
return this.launchpadClientInstance.createLaunchpadOffer(
createOfferRequest
);
} else if (createOfferRequest.walletProvider === WALLET_PROVIDER.xverse) {
const offer: CreateLaunchpadOfferResponse =
await this.launchpadClientInstance.createLaunchpadOffer(
createOfferRequest
);
const sellerInput = {
address: createOfferRequest.buyerPaymentAddress,
signingIndexes: offer.buyerInputIndices,
};
const payload = {
network: {
type: this.network,
},
message: "Sign Buyer Transaction",
psbtBase64: offer.psbt,
broadcast: false,
inputsToSign: [sellerInput],
};
return new Promise(async (resolve, reject) => {
const response: SatsConnectWrapperResponse =
await this.satsConnectWrapper(payload);
if (response && response.success && response.psbtBase64) {
/** this Response will be used for the next submit offer request */
const submitLaunchpadOfferRequest: SubmitLaunchpadOfferRequest = {
ordinalId: offer.ordinalId,
launchpadPhase: offer.launchpadPhase,
signedBuyerPSBTBase64: response.psbtBase64,
};
resolve(submitLaunchpadOfferRequest);
} else {
console.log("Transaction canceled");
}
});
} else {
throw new Error("Wallet not supported");
}
}
/**
* submits the launchpad offer and gets the tansaction id
* @param {SubmitLaunchpadOfferRequest} submitLaunchpadOfferRequest - The request object for create Launchpad Offer.
* @returns {Promise<SubmitLaunchpadOfferResponse>} A promise that resolves to the response from the API.
*/
async submitLaunchpadOffer(
submitLaunchpadOfferRequest: SubmitLaunchpadOfferRequest
): Promise<SubmitLaunchpadOfferResponse> {
return this.launchpadClientInstance.submitLaunchpadOffer(
submitLaunchpadOfferRequest
);
}
/**
* Sats connect Wrapper method to sign transactions
* @param {SignTransactionPayload} payload The request payload for transaction.
* @returns {Promise<SatsConnectWrapperResponse>} A promise that resolves to the response of transaction.
*/
async satsConnectWrapper(
payload: SignTransactionPayload
): Promise<SatsConnectWrapperResponse> {
return new Promise((resolve, reject) => {
signTransaction({
payload,
onFinish: async (response) => {
resolve({
success: true,
message: "Transaction successfull",
...response,
});
},
onCancel: () => {
resolve({ success: false, message: "Transaction cancelled" });
},
});
});
}
}