DEV Community

Swatantra goswami
Swatantra goswami

Posted on

BREEZ SDK NODELESS INTEGRATION in WEB

import init, {
    defaultConfig,
    SdkBuilder,
} from "@breeztech/breez-sdk-spark/web";
import { config, IS_SANDBOX } from 'dok-wallet-blockchain-networks/config/config';

class JsLogger {
    log = (logEntry) => {
        console.log(
            `[${new Date().toISOString()} ${logEntry.level}]: ${logEntry.line}`
        );
    };
}

class JsEventListener {
    constructor(callback) {
        this.callback = callback;
    }

    onEvent = event => {
        if (this.callback) {
            this.callback(event);
        }
    };
}


function decimalStringToBigInt(value, decimals) {
    if (!/^\d+(\.\d+)?$/.test(value)) {
        throw new Error('Invalid decimal string');
    }

    const [intPart, fracPart = ''] = value.split('.');
    const paddedFrac = (fracPart + '0'.repeat(decimals)).slice(0, decimals);

    return BigInt(intPart + paddedFrac);
}

let sdkInstance = null;
let connectingPromise = null;
let prepareSendResponse;
const sdkMap = new Map();

const commonConnectSdk = async mnemonic => {
    try {

        // Initialise the WebAssembly module
        await init();
        // "mainnet" | "regtest";
        const network = IS_SANDBOX ? "regtest" : "mainnet";
        console.log("network:", network)
        // Connect using the config
        let config = defaultConfig(network);
        config.apiKey = process.env.BREEZ_API_KEY;

        let sdkBuilder = SdkBuilder.new(config, {
            type: "mnemonic",
            mnemonic: mnemonic,
        });

        sdkBuilder = await sdkBuilder.withDefaultStorage("./.data");

        sdkInstance = await sdkBuilder.build();

        // await sdkInstance.addEventListener(eventListener);
        sdkMap.set(mnemonic, sdkInstance);
        console.log('✅ Breez SDK connected');
        return sdkInstance;
    } catch (err) {
        console.error('❌ Connection error:', err);
        sdkInstance = null;
        connectingPromise = null;
        throw err;
    }
};

async function connectToSdk(phrase) {
    let mnemonic = phrase;
    console.log("sdkMap:", sdkMap)
    if (sdkInstance) {
        if (sdkMap.has(mnemonic)) {
            console.log('♻️ Reusing SDK');
            return sdkMap.get(mnemonic);
        } else {
            // Initialize sdkInstance for the new mnemonic
            if (!mnemonic) return sdkInstance;
            connectingPromise = commonConnectSdk(mnemonic);
            return connectingPromise;
        }
    }

    if (connectingPromise) {
        return connectingPromise;
    }

    connectingPromise = commonConnectSdk(mnemonic);

    return connectingPromise;
}

async function prepareAndSendPayment(phrase, paymentRequest, amount) {
    try {
        const sdk = await connectToSdk(phrase);
        if (!sdk || !paymentRequest) {
            Alert.alert('Error', 'SDK not connected or no payment request');
            return;
        }
        const prepareResponse = await sdk.prepareSendPayment({
            paymentRequest,
            amount: decimalStringToBigInt(amount, 8),
        });
        console.log("prepareResponse:", prepareResponse)
        prepareSendResponse = prepareResponse;
        if (prepareResponse.paymentMethod.type ===  "bitcoinAddress") {
            const lightningFee =
                prepareResponse.paymentMethod.lightningFeeSats;
            const sparkFee =
                prepareResponse.paymentMethod.sparkTransferFeeSats;

            return {
                lightningFee: lightningFee,
                sparkFee: sparkFee,
            };
        }
        if (prepareResponse.paymentMethod?.type === "sparkAddress") {
            const feeSats = prepareResponse.paymentMethod.fee;
            return {
                lightningFee: feeSats,
                sparkFee: '',
            };
        }
        return {};
    } catch (err) {
        console.error('Error preparing payment:', err);
        Alert.alert('Prepare Error', err.message);
    }
}

function satoshiToBtc(sats) {
    if (sats === null || sats === undefined) return 0;

    // Handle BigInt or number or string safely
    const satsNumber = typeof sats === 'bigint' ? Number(sats) : Number(sats);

    return satsNumber / 100_000_000;
}

export const getLightningBalance = async (phrase) => {
    const sdk = await connectToSdk(phrase)
    try {
        const sdk = await connectToSdk(phrase);
        const obj1 = Object.fromEntries(sdkMap);
        const info = await sdk.getInfo({});
        return info.balanceSats;
    } catch (error) {
        console.log(error);
    }
}

export const isLightningAddressValid = async (address, phrase) => {
    try {
        const sdk = await connectToSdk(phrase);
        if (!sdk) {
            Alert.alert('Error', 'SDK not connected or no payment request');
            return;
        }
        const input = await sdk.parse(address);
        if (input.type ===  "bitcoinAddress") {
            return false;
        } else if (input.type === "bolt11Invoice") {
            return true;
        } else if (input.type === "sparkAddress") {
            return true;
        }
        return false;
    } catch (error) {
        console.log(error)
        return false;
    }
}

export const generateLightningInvoiceViaBolt11 = async (phrase) => {
    try {
        const sdk = await connectToSdk(phrase);
        if (!sdk) {
            Alert.alert('Error', 'SDK not connected');
            return;
        }
        const response = await sdk.receivePayment({
            paymentMethod: { type: "bolt11Invoice", description: "Dokwallet Invoice" },

        });
        return {
            address: response.paymentRequest,
            receiveFeeSats: response.fee,
        };
    } catch (error) {
        console.error('Error generating invoice:', error);
        Alert.alert('Invoice Error', error.message);
    }
}

export const generateLightningSparkAddress = async (phrase) => {
    try {
        const sdk = await connectToSdk(phrase);
        console.log("sdk:", sdk)
        if (!sdk) {
            console.error('Error', 'SDK not connected');
            return;
        }
        const response = await sdk.receivePayment({
            paymentMethod: { type: 'sparkAddress' },
        });
        console.log("response:", response)
        return {
            address: response.paymentRequest,
            receiveFeeSats: response.fee,
        };
    } catch (error) {
        console.error('Error generating invoice:', error);
    }
}

export const generateLightningInvoiceViaBitcoinAddress = async (phrase) => {
    try {
        const sdk = await connectToSdk(phrase);
        if (!sdk) {
            Alert.alert('Error', 'SDK not connected');
            return;
        }
        const response = await sdk.receivePayment({
            paymentMethod: { type: "bitcoinAddress" },
        });

        return {
            address: response.paymentRequest,
            receiveFeeSats: response.fee,
        };
    } catch (err) {
        console.error('Error generating invoice:', err);
        Alert.alert('Invoice Error', err.message);
    }
}
// sparkrt1pgss833snj2n4plhav04s58zxhc2ury29dhcpe4tehcx80rgc5kypfr9xzrhgn
export const prepareLightning = async (phrase, toAddress, amount) => {
    try {
        const { lightningFee } = await prepareAndSendPayment(phrase, toAddress, amount);
        const fee = satoshiToBtc(lightningFee);
        console.log('fee:', fee);
        return {
            fee: fee,
            estimateGas: 0,
            feesOptions: [],
        };
    } catch (error) {
        console.error('Error in bitcoin gas fee', error);
        throw error;
    }
}

export const sendLightning = async (phrase) => {
    try {
        const sdk = await connectToSdk(phrase);
        if (!sdk || !prepareSendResponse) {
           console.log('Error', 'SDK not connected or no payment request');
            return;
        }

        // Send the token payment
        const sendResponse = await sdk.sendPayment({
            prepareResponse: prepareSendResponse,
            options: undefined,
            idempotencyKey: undefined,
        });
        console.log("sendResponse:", sendResponse)
        const payment = sendResponse.payment;
        return payment.id;
    } catch (error) {
        console.error('Error in bitcoin gas fee', error);
        throw error;
    }
}

export const waitForLightningConfirmation = async (phrase) => {
    const sdk = await connectToSdk(phrase);

    if (!sdk) {
       console.log('Error', 'SDK not connected');
        return;
    }

    return new Promise(async (resolve, reject) => {
        let listenerId = null;
        let timeoutId = null;
        let resolved = false;

        try {
            const eventListener = new JsEventListener(async event => {
                console.log('event:', event);

                if (resolved) return;

               if (event.type === 'PaymentSucceeded' || event.type === 'synced') {
                    resolved = true;

                    // � cleanup
                    if (listenerId !== null) {
                        sdk.removeEventListener(listenerId);
                    }
                    if (timeoutId) {
                        clearTimeout(timeoutId);
                    }

                    resolve(true);
                }
            });

            listenerId = await sdk.addEventListener(eventListener);
            console.log('Event listener registered:', listenerId);

            // ⏱️ 90 seconds timeout
            timeoutId = setTimeout(() => {
                if (resolved) return;

                resolved = true;

                console.log('⏱️ Payment confirmation timeout (90s)');

                // � cleanup
                if (listenerId !== null) {
                    sdk.removeEventListener(listenerId);
                }

                resolve('pending');
            }, 90_000); // 90 seconds
        } catch (error) {
            console.error('Error in waitForConfirmation:', error);

            // � cleanup
            if (listenerId !== null) {
                sdk.removeEventListener(listenerId);
            }
            if (timeoutId) {
                clearTimeout(timeoutId);
            }

            reject(error);
        }
    });
}

export const getLightningTransactions = async (phrase) => {
    try {
        const sdk = await connectToSdk(phrase);
        if (!sdk) return;

        const response = await sdk.listPayments({
            offset: undefined,
            limit: 20,
        });
        const transactions = response.payments;
        console.log('transactions:', transactions);
        if (Array.isArray(transactions)) {
            return transactions.map(item => {
                const txHash = item?.details.inner?.paymentHash || item?.id || 'N/A';
                return {
                    amount: item.amount,
                    link: txHash?.substring(0, 13) + '...',
                    url: `${config.BITCOIN_LIGHTNING_URL}/tx/${txHash}`,
                    status: item?.status !== "completed" ? 'Pending' : 'SUCCESS',
                    date: Number(item?.timestamp) * 1000,
                    from: item?.details.inner?.preimage,
                    to: item?.details.inner?.destinationPubKey,
                    totalCourse: '0$',
                    paymentType: item.paymentType,
                };
            });
        }
        return [];
    } catch (error) {
        console.error(
            `error getting transactions for bitcoin lightning ${error}`,
        );
        return [];
    }
}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)