import { ApiError, CheckSpendablePayload, CheckSpendableResponse, GetInfoResponse, MeltPayload, MeltResponse, MintKeys, RequestMintResponse, SerializedBlindedMessage, SerializedBlindedSignature, SplitPayload, SplitResponse, } from "./model/types/index.js"; import { checkResponse, checkResponseError, isObj } from "./utils.js"; /** * Class represents Cashu Mint API. This class contains Lower level functions that are implemented by CashuWallet. */ class CashuMint { /** * @param _mintUrl requires mint URL to create this object */ constructor(private _mintUrl: string) {} get mintUrl() { return this._mintUrl; } /** * fetches mints info at the /info endpoint * @param mintUrl */ public static async getInfo(mintUrl: string): Promise { const res = await fetch(`${mintUrl}/info/`); const data: GetInfoResponse = await res.json(); return data; } /** * fetches mints info at the /info endpoint */ async getInfo(): Promise { return CashuMint.getInfo(this._mintUrl); } /** * Starts a minting process by requesting an invoice from the mint * @param mintUrl * @param amount Amount requesting for mint. * @returns the mint will create and return a Lightning invoice for the specified amount */ public static async requestMint( mintUrl: string, amount: number, ): Promise { const res = await fetch(`${mintUrl}/mint?amount=${amount}/`); const data = await res.json(); return data; } /** * Starts a minting process by requesting an invoice from the mint * @param amount Amount requesting for mint. * @returns the mint will create and return a Lightning invoice for the specified amount */ async requestMint(amount: number): Promise { return CashuMint.requestMint(this._mintUrl, amount); } /** * Requests the mint to perform token minting after the LN invoice has been paid * @param mintUrl * @param payloads outputs (Blinded messages) that can be written * @param hash hash (id) used for by the mint to keep track of wether the invoice has been paid yet * @returns serialized blinded signatures */ public static async mint( mintUrl: string, payloads: { outputs: Array }, hash: string, ) { try { const res = await fetch(`${mintUrl}/mint?hash=${hash}/`, { method: "POST", body: JSON.stringify(payloads), headers: { "Accept": "application/json", "Content-Type": "application/json", }, }); const data: & { promises: Array } & ApiError = await res.json(); checkResponse(data); if (!isObj(data) || !Array.isArray(data?.promises)) { throw new Error("bad response"); } return data; } catch (err) { checkResponseError(err); throw err; } } /** * Requests the mint to perform token minting after the LN invoice has been paid * @param payloads outputs (Blinded messages) that can be written * @param hash hash (id) used for by the mint to keep track of wether the invoice has been paid yet * @returns serialized blinded signatures */ async mint( payloads: { outputs: Array }, hash: string, ) { return CashuMint.mint(this._mintUrl, payloads, hash); } /** * Get the mints public keys * @param mintUrl * @param keysetId optional param to get the keys for a specific keyset. If not specified, the keys from the active keyset are fetched * @returns */ public static async getKeys( mintUrl: string, keysetId?: string, ): Promise { if (keysetId) { // make the keysetId url safe keysetId = keysetId.replace(/\//g, "_").replace(/\+/g, "-"); } const res = await fetch(`${mintUrl}/keys${keysetId ? `/${keysetId}/` : "/"}`); const data = await res.json(); return data; } /** * Get the mints public keys * @param keysetId optional param to get the keys for a specific keyset. If not specified, the keys from the active keyset are fetched * @returns the mints public keys */ async getKeys(keysetId?: string): Promise { return CashuMint.getKeys(this._mintUrl, keysetId); } /** * Get the mints keysets in no specific order * @param mintUrl * @returns all the mints past and current keysets. */ public static async getKeySets( mintUrl: string, ): Promise<{ keysets: Array }> { const res = await fetch(`${mintUrl}/keysets/`); const data = await res.json(); return data; } /** * Get the mints keysets in no specific order * @returns all the mints past and current keysets. */ async getKeySets(): Promise<{ keysets: Array }> { return CashuMint.getKeySets(this._mintUrl); } /** * Ask mint to perform a split operation * @param mintUrl * @param splitPayload data needed for performing a token split * @returns split tokens */ public static async split( mintUrl: string, splitPayload: SplitPayload, ): Promise { try { const res = await fetch(`${mintUrl}/split/`, { method: "POST", body: JSON.stringify(splitPayload), headers: { "Accept": "application/json", "Content-Type": "application/json", }, }); const data: SplitResponse = await res.json(); checkResponse(data); if ( !isObj(data) || !Array.isArray(data?.fst) || !Array.isArray(data?.snd) ) { throw new Error("bad response"); } return data; } catch (err) { checkResponseError(err); throw err; } } /** * Ask mint to perform a split operation * @param splitPayload data needed for performing a token split * @returns split tokens */ async split(splitPayload: SplitPayload): Promise { return CashuMint.split(this._mintUrl, splitPayload); } /** * Ask mint to perform a melt operation. This pays a lightning invoice and destroys tokens matching its amount + fees * @param mintUrl * @param meltPayload * @returns */ public static async melt( mintUrl: string, meltPayload: MeltPayload, ): Promise { try { const res = await fetch(`${mintUrl}/melt/`, { method: "POST", body: JSON.stringify(meltPayload), headers: { "Accept": "application/json", "Content-Type": "application/json", }, }); const data: MeltResponse = await res.json(); checkResponse(data); if ( !isObj(data) || typeof data?.paid !== "boolean" || (data?.preimage !== null && typeof data?.preimage !== "string") ) { throw new Error("bad response"); } return data; } catch (err) { checkResponseError(err); throw err; } } /** * Ask mint to perform a melt operation. This pays a lightning invoice and destroys tokens matching its amount + fees * @param meltPayload * @returns */ async melt(meltPayload: MeltPayload): Promise { return CashuMint.melt(this._mintUrl, meltPayload); } /** * Estimate fees for a given LN invoice * @param mintUrl * @param checkfeesPayload Payload containing LN invoice that needs to get a fee estimate * @returns estimated Fee */ public static async checkFees( mintUrl: string, checkfeesPayload: { pr: string }, ): Promise<{ fee: number }> { try { const res = await fetch(`${mintUrl}/checkfees/`, { method: "POST", body: JSON.stringify(checkfeesPayload), headers: { "Accept": "application/json", "Content-Type": "application/json", }, }); const data: { fee: number } & ApiError = await res.json(); checkResponse(data); if (!isObj(data) || typeof data?.fee !== "number") { throw new Error("bad response"); } return data; } catch (err) { checkResponseError(err); throw err; } } /** * Estimate fees for a given LN invoice * @param mintUrl * @param checkfeesPayload Payload containing LN invoice that needs to get a fee estimate * @returns estimated Fee */ async checkFees(checkfeesPayload: { pr: string }): Promise<{ fee: number }> { return CashuMint.checkFees(this._mintUrl, checkfeesPayload); } /** * Checks if specific proofs have already been redeemed * @param mintUrl * @param checkPayload * @returns redeemed and unredeemed ordered list of booleans */ public static async check( mintUrl: string, checkPayload: CheckSpendablePayload, ): Promise { try { const res = await fetch(`${mintUrl}/check/`, { method: "POST", body: JSON.stringify(checkPayload), headers: { "Accept": "application/json", "Content-Type": "application/json", }, }); const data: CheckSpendableResponse = await res.json(); checkResponse(data); if (!isObj(data) || !Array.isArray(data?.spendable)) { throw new Error("bad response"); } return data; } catch (err) { checkResponseError(err); throw err; } } /** * Checks if specific proofs have already been redeemed * @param checkPayload * @returns redeemed and unredeemed ordered list of booleans */ async check( checkPayload: CheckSpendablePayload, ): Promise { return CashuMint.check(this._mintUrl, checkPayload); } } export { CashuMint };