DEV Community

Swatantra goswami
Swatantra goswami

Posted on • Edited on

Bitcoin lightning network

We will use https://breez.technology/sdk/ SDK for lightning network
here is the documentation of that https://sdk-doc-spark.breez.technology/guide/install.html

NOTE Conversion rate

🔹 1 BTC = 100,000,000 satoshis // 10 cr ( 1 cr has 7 zeros)
🔹 10,000 satoshis = 0.0001 BTC
🔹 USD value = 0.0001 × current BTC price in USD
Enter fullscreen mode Exit fullscreen mode
import {
  SafeAreaView,
  Text,
  TouchableOpacity,
  View,
  TextInput,
  ScrollView,
  StyleSheet,
  Alert,
} from 'react-native';
import {
  connect,
  defaultConfig,
  Network,
  Seed,
  ReceivePaymentMethod,
  SendPaymentMethod,
} from '@breeztech/breez-sdk-spark-react-native';
import {useState, useEffect} from 'react';
import { DocumentDirectoryPath } from 'react-native-fs';

// Event Listener Class
class JsEventListener {
  constructor(callback) {
    this.callback = callback;
  }

  onEvent = event => {
    console.log(`Received event: ${JSON.stringify(event)}`);
    if (this.callback) {
      this.callback(event);
    }
  };
}

export default function BreezSpark() {
   const [sdk, setSdk] = useState(null);
  const [balance, setBalance] = useState(null);
  const [events, setEvents] = useState([]);
  const [listenerId, setListenerId] = useState(null);
  const [payments, setPayments] = useState([]);

  // For receiving payments
  const [invoiceDescription, setInvoiceDescription] = useState('');
  const [invoiceAmount, setInvoiceAmount] = useState('');
  const [generatedInvoice, setGeneratedInvoice] = useState('');
  const [receiveFeeSats, setReceiveFeeSats] = useState(null);

  // For sending payments
  const [paymentRequest, setPaymentRequest] = useState('');
  const [sendAmount, setSendAmount] = useState('');
  const [preparedPayment, setPreparedPayment] = useState(null);

  async function connectToSdk() {
    try {
      const mnemonic = ''; // 9901 sats
      // const mnemonic = ''; //    10001
      let config = defaultConfig(Network.Regtest);
      config.apiKey = '';

      const baseDir = DocumentDirectoryPath.replace('file://', '');
      const workingDir = `${baseDir}breezSdkSpark`;

    //   const directoryPath = await getOrCreateDirectory('testing', workingDir);

      console.log('API Key configured:', !!config.apiKey);

      const seed = new Seed.Mnemonic({mnemonic});
      const connectedSdk = await connect({
        config,
        seed,
        storageDir: workingDir,
      });

      setSdk(connectedSdk);
      console.log('✅ Connected successfully!');

      // Start listening to events
      setupEventListener(connectedSdk);

      // Get initial balance
      await fetchBalance(connectedSdk);

      // Load payment history
      await fetchPayments(connectedSdk);
    } catch (err) {
      console.error('❌ Connection error:', err);
      Alert.alert('Connection Error', err.message);
    }
  }

  function setupEventListener(sdkInstance) {
    const eventListener = new JsEventListener(event => {
      setEvents(prev =>
        [{event, timestamp: new Date().toISOString()}, ...prev].slice(0, 10),
      );

      // Refresh balance on payment events
      if (
        event.type === 'paymentSucceeded' ||
        event.type === 'paymentReceived'
      ) {
        fetchBalance(sdkInstance);
        fetchPayments(sdkInstance);
      }
    });

    const id = sdkInstance.addEventListener(eventListener);
    setListenerId(id);
    console.log('Event listener registered:', id);
  }

  async function fetchBalance(sdkInstance = sdk) {
    if (!sdkInstance) return;

    try {
      const info = await sdkInstance.getInfo({});
      setBalance(info.balanceSats);
      console.log('Balance:', info.balanceSats);
    } catch (err) {
      console.error('Error fetching balance:', err);
      Alert.alert('Balance Error', err.message);
    }
  }

  async function fetchPayments(sdkInstance = sdk) {
    if (!sdkInstance) return;

    try {
      const response = await sdkInstance.listPayments({
        offset: undefined,
        limit: 20,
      });
      console.log("response=>",response)
      setPayments(response.payments);
      console.log('Payments loaded:', response.payments.length);
    } catch (err) {
      console.error('Error fetching payments:', err);
    }
  }

  async function generateInvoice() {
    if (!sdk) {
      Alert.alert('Error', 'SDK not connected');
      return;
    }

    try {
      const amountSats = invoiceAmount ? BigInt(invoiceAmount) : undefined;

      // NOTE vai invoice
      const response = await sdk.receivePayment({
        paymentMethod: new ReceivePaymentMethod.Bolt11Invoice({
          description: invoiceDescription || 'Payment',
          amountSats,
        }),
      });
      console.log("response=>", response)
      // NOTE vai btc address ub regtest
      // const response = await sdk.receivePayment({
      //   paymentMethod: new ReceivePaymentMethod.BitcoinAddress()
      // })

      console.log(`Payment Request: ${paymentRequest}`)
      setGeneratedInvoice(response.paymentRequest);
      setReceiveFeeSats(response.fee);
      console.log('Invoice generated:', response.paymentRequest);
      console.log('Receive fees:', response.fee);

      Alert.alert('Invoice Generated', 'Check the invoice field below');
    } catch (err) {
      console.error('Error generating invoice:', err);
      Alert.alert('Invoice Error', err.message);
    }
  }

  async function preparePayment() {
    if (!sdk || !paymentRequest) {
      Alert.alert('Error', 'SDK not connected or no payment request');
      return;
    }

    try {
      const amountSats = sendAmount ? BigInt(sendAmount) : undefined;

      const prepareResponse = await sdk.prepareSendPayment({
        paymentRequest,
        amountSats,
      });

      setPreparedPayment(prepareResponse);

      if (
        prepareResponse.paymentMethod instanceof SendPaymentMethod.Bolt11Invoice
      ) {
        const lightningFee =
          prepareResponse.paymentMethod.inner.lightningFeeSats;
        const sparkFee =
          prepareResponse.paymentMethod.inner.sparkTransferFeeSats;

        Alert.alert(
          'Payment Prepared',
          `Lightning Fees: ${lightningFee} sats\nSpark Fees: ${
            sparkFee || 'N/A'
          } sats`,
          [
            {text: 'Cancel', style: 'cancel'},
            {text: 'Send Payment', onPress: () => sendPayment(prepareResponse)},
          ],
        );
      }
    } catch (err) {
      console.error('Error preparing payment:', err);
      Alert.alert('Prepare Error', err.message);
    }
  }

  async function sendPayment(prepareResponse) {
    if (!sdk) return;

    try {
      const response = await sdk.sendPayment({
        prepareResponse,
      });

      console.log('Payment sent:', response);
      Alert.alert('Success', 'Payment sent successfully!');

      // Clear form and refresh
      setPaymentRequest('');
      setSendAmount('');
      setPreparedPayment(null);
      await fetchBalance();
      await fetchPayments();
    } catch (err) {
      console.error('Error sending payment:', err);
      Alert.alert('Send Error', err.message);
    }
  }

  useEffect(() => {
    return () => {
      if (sdk && listenerId) {
        sdk.removeEventListener(listenerId);
      }
    };
  }, [sdk, listenerId]);

  console.log("receiveFeeSats=>", receiveFeeSats)
  return (
    <SafeAreaView style={styles.container}>
      <ScrollView style={styles.scrollView}>
        {/* Connection Section */}
        <View style={styles.section}>
          <TouchableOpacity
            onPress={connectToSdk}
            style={[styles.button, sdk && styles.buttonConnected]}
            disabled={!!sdk}>
            <Text style={styles.buttonText}>
              {sdk ? '✅ Connected' : 'Connect to Breez SDK'}
            </Text>
          </TouchableOpacity>

          {balance !== null && (
            <View style={styles.balanceContainer}>
              <Text style={styles.balanceLabel}>Balance:</Text>
              <Text style={styles.balanceAmount}>
                {balance.toString()} sats
              </Text>
              <TouchableOpacity
                onPress={() => fetchBalance()}
                style={styles.refreshButton}>
                <Text>🔄 Refresh</Text>
              </TouchableOpacity>
            </View>
          )}
        </View>

        {/* Receive Payment Section */}
        {sdk && (
          <View style={styles.section}>
            <Text style={styles.sectionTitle}>Receive Payment</Text>
            <TextInput
              style={styles.input}
              placeholder="Invoice description"
              placeholderTextColor="#9CA3AF" 
              value={invoiceDescription}
              onChangeText={setInvoiceDescription}
            />
            <TextInput
              style={styles.input}
              placeholder="Amount (sats) - optional"
              placeholderTextColor="#9CA3AF" 
              value={invoiceAmount}
              onChangeText={setInvoiceAmount}
              keyboardType="number-pad"
            />
            <TouchableOpacity onPress={generateInvoice} style={styles.button}>
              <Text style={styles.buttonText}>Generate Invoice</Text>
            </TouchableOpacity>

            {generatedInvoice && (
              <View style={styles.invoiceContainer}>
                <Text style={styles.label}>Invoice:</Text>
                <Text style={styles.invoice} selectable>
                  {generatedInvoice}
                </Text>
                {/* {receiveFeeSats !== null && (
                  <Text style={styles.feeText}>
                    Fee: {receiveFeeSats.toString()} sats
                  </Text>
                )} */}
              </View>
            )}
          </View>
        )}

        {/* Send Payment Section */}
        {sdk && (
          <View style={styles.section}>
            <Text style={styles.sectionTitle}>Send Payment</Text>
            <TextInput
              style={[styles.input, styles.textArea]}
              placeholder="Paste Lightning invoice (bolt11)"
              placeholderTextColor="#9CA3AF" 
              value={paymentRequest}
              onChangeText={setPaymentRequest}
              multiline
            />
            <TextInput
              style={styles.input}
              placeholder="Amount (sats) - optional"
              placeholderTextColor="#9CA3AF" // gray, change as needed
              value={sendAmount}
              onChangeText={setSendAmount}
              keyboardType="number-pad"

            />
            <TouchableOpacity onPress={preparePayment} style={styles.button}>
              <Text style={styles.buttonText}>Prepare & Send Payment</Text>
            </TouchableOpacity>
          </View>
        )}

        {/* Events Section */}
        {events.length > 0 && (
          <View style={styles.section}>
            <Text style={styles.sectionTitle}>
              Recent Events ({events.length})
            </Text>
            {events.map((item, idx) => (
              <View key={idx} style={styles.eventItem}>
                <Text style={styles.eventTime}>{item.timestamp}</Text>
                <Text style={styles.eventText}>
                  {JSON.stringify(item.event, null, 2)}
                </Text>
              </View>
            ))}
          </View>
        )}

        {/* Payments History */}
        {payments.length > 0 && (
          <View style={styles.section}>
            <Text style={styles.sectionTitle}>
              Payment History ({payments.length})
            </Text>
            {payments.slice(0, 5).map((payment, idx) => (
              <View key={idx} style={styles.paymentItem}>
                <Text style={styles.paymentAmount}>
                  {payment.amountSats?.toString() || 'N/A'} sats
                </Text>
                <Text style={styles.paymentStatus}>{payment.status}</Text>
              </View>
            ))}
          </View>
        )}
      </ScrollView>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  scrollView: {
    flex: 1,
    padding: 16,
  },
  section: {
    backgroundColor: 'white',
    borderRadius: 12,
    padding: 16,
    marginBottom: 16,
    shadowColor: '#000',
    shadowOffset: {width: 0, height: 2},
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 12,
    color: '#333',
  },
  button: {
    backgroundColor: '#007AFF',
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
  },
  buttonConnected: {
    backgroundColor: '#34C759',
  },
  buttonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: '600',
  },
  balanceContainer: {
    marginTop: 16,
    padding: 16,
    backgroundColor: '#f8f8f8',
    borderRadius: 8,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  balanceLabel: {
    fontSize: 16,
    color: '#666',
  },
  balanceAmount: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#007AFF',
    flex: 1,
    marginLeft: 8,
  },
  refreshButton: {
    padding: 8,
  },
  input: {
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 8,
    padding: 12,
    marginBottom: 12,
    fontSize: 16,
  },
  textArea: {
    height: 80,
    textAlignVertical: 'top',
  },
  invoiceContainer: {
    marginTop: 12,
    padding: 12,
    backgroundColor: '#f8f8f8',
    borderRadius: 8,
  },
  label: {
    fontSize: 14,
    fontWeight: '600',
    marginBottom: 8,
    color: '#666',
  },
  invoice: {
    fontSize: 12,
    color: '#333',
    fontFamily: 'monospace',
  },
  feeText: {
    marginTop: 8,
    fontSize: 14,
    color: '#666',
  },
  eventItem: {
    padding: 12,
    backgroundColor: '#f8f8f8',
    borderRadius: 8,
    marginBottom: 8,
  },
  eventTime: {
    fontSize: 12,
    color: '#666',
    marginBottom: 4,
  },
  eventText: {
    fontSize: 12,
    fontFamily: 'monospace',
    color: '#333',
  },
  paymentItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    padding: 12,
    backgroundColor: '#f8f8f8',
    borderRadius: 8,
    marginBottom: 8,
  },
  paymentAmount: {
    fontSize: 16,
    fontWeight: '600',
    color: '#333',
  },
  paymentStatus: {
    fontSize: 14,
    color: '#666',
  },
});
Enter fullscreen mode Exit fullscreen mode

Use Two Screen

import {
  SafeAreaView,
  Text,
  TouchableOpacity,
  View,
  TextInput,
  ScrollView,
  StyleSheet,
  Alert,
} from 'react-native';
import {
  connect,
  defaultConfig,
  Network,
  Seed,
  ReceivePaymentMethod,
  SendPaymentMethod,
} from '@breeztech/breez-sdk-spark-react-native';
import React, {useState, useEffect} from 'react';
import {DocumentDirectoryPath} from 'react-native-fs';

import {SceneMap, TabBar, TabView} from 'react-native-tab-view';
import {SCREEN_WIDTH} from '@gorhom/bottom-sheet';

const ROUTES = [
  {key: 'add_coins', title: 'Coins '},
  {key: 'add_coins_group', title: 'Coins Group '},
];

// Event Listener Class
class JsEventListener {
  constructor(callback) {
    this.callback = callback;
  }

  onEvent = event => {
    console.log(`Received event: ${JSON.stringify(event)}`);
    if (this.callback) {
      this.callback(event);
    }
  };
}

const RenderTabBar = props => {
  const {styles} = props;

  // Create options for each route
  const tabOptions = ROUTES.reduce((acc, route) => {
    acc[route.key] = {
      label: ({focused}) => (
        <Text style={[styles.tabLabel, focused && styles.tabLabelFocused]}>
          {route.title}
        </Text>
      ),
    };
    return acc;
  }, {});

  return (
    <TabBar
      {...props}
      indicatorStyle={styles.indicator}
      style={styles.tabBar}
      options={tabOptions}
    />
  );
};

function BreezSpark1() {
  const [sdk, setSdk] = useState(null);
  const [balance, setBalance] = useState(null);
  const [events, setEvents] = useState([]);
  const [listenerId, setListenerId] = useState(null);
  const [payments, setPayments] = useState([]);

  // For receiving payments
  const [invoiceDescription, setInvoiceDescription] = useState('');
  const [invoiceAmount, setInvoiceAmount] = useState('');
  const [generatedInvoice, setGeneratedInvoice] = useState('');
  const [receiveFeeSats, setReceiveFeeSats] = useState(null);

  // For sending payments
  const [paymentRequest, setPaymentRequest] = useState('');
  const [sendAmount, setSendAmount] = useState('');
  const [preparedPayment, setPreparedPayment] = useState(null);

  async function connectToSdk() {
    try {
      const mnemonic = ''; // 9901 sats
      // const mnemonic = ''; //    10001
      let config = defaultConfig(Network.Regtest);
      config.apiKey = '';

      const baseDir = DocumentDirectoryPath.replace('file://', '');
      const workingDir = `${baseDir}breezSdkSpark`;

      //   const directoryPath = await getOrCreateDirectory('testing', workingDir);

      console.log('API Key configured:', !!config.apiKey);

      const seed = new Seed.Mnemonic({mnemonic});
      const connectedSdk = await connect({
        config,
        seed,
        storageDir: workingDir,
      });

      setSdk(connectedSdk);
      console.log('✅ Connected successfully!');

      // Start listening to events
      setupEventListener(connectedSdk);

      // Get initial balance
      await fetchBalance(connectedSdk);

      // Load payment history
      await fetchPayments(connectedSdk);
    } catch (err) {
      console.error('❌ Connection error:', err);
      Alert.alert('Connection Error', err.message);
    }
  }

  function setupEventListener(sdkInstance) {
    const eventListener = new JsEventListener(event => {
      setEvents(prev =>
        [{event, timestamp: new Date().toISOString()}, ...prev].slice(0, 10),
      );

      // Refresh balance on payment events
      if (
        event.type === 'paymentSucceeded' ||
        event.type === 'paymentReceived'
      ) {
        fetchBalance(sdkInstance);
        fetchPayments(sdkInstance);
      }
    });

    const id = sdkInstance.addEventListener(eventListener);
    setListenerId(id);
    console.log('Event listener registered:', id);
  }

  async function fetchBalance(sdkInstance = sdk) {
    if (!sdkInstance) return;

    try {
      const info = await sdkInstance.getInfo({});
      setBalance(info.balanceSats);
      console.log('Balance:', info.balanceSats);
    } catch (err) {
      console.error('Error fetching balance:', err);
      Alert.alert('Balance Error', err.message);
    }
  }

  async function fetchPayments(sdkInstance = sdk) {
    if (!sdkInstance) return;

    try {
      const response = await sdkInstance.listPayments({
        offset: undefined,
        limit: 20,
      });
      console.log('response=>', response);
      setPayments(response.payments);
      console.log('Payments loaded:', response.payments.length);
    } catch (err) {
      console.error('Error fetching payments:', err);
    }
  }

  async function generateInvoice() {
    if (!sdk) {
      Alert.alert('Error', 'SDK not connected');
      return;
    }

    try {
      const amountSats = invoiceAmount ? BigInt(invoiceAmount) : undefined;

      // NOTE vai invoice
      const response = await sdk.receivePayment({
        paymentMethod: new ReceivePaymentMethod.Bolt11Invoice({
          description: invoiceDescription || 'Payment',
          amountSats,
        }),
      });
      console.log('response=>', response);
      // NOTE vai btc address ub regtest
      // const response = await sdk.receivePayment({
      //   paymentMethod: new ReceivePaymentMethod.BitcoinAddress()
      // })

      console.log(`Payment Request: ${paymentRequest}`);
      setGeneratedInvoice(response.paymentRequest);
      setReceiveFeeSats(response.fee);
      console.log('Invoice generated:', response.paymentRequest);
      console.log('Receive fees:', response.fee);

      Alert.alert('Invoice Generated', 'Check the invoice field below');
    } catch (err) {
      console.error('Error generating invoice:', err);
      Alert.alert('Invoice Error', err.message);
    }
  }

  async function preparePayment() {
    if (!sdk || !paymentRequest) {
      Alert.alert('Error', 'SDK not connected or no payment request');
      return;
    }

    try {
      const amountSats = sendAmount ? BigInt(sendAmount) : undefined;
      console.log('paymentRequest: ', paymentRequest);
      console.log('amountSats: ', amountSats);
      const prepareResponse = await sdk.prepareSendPayment({
        paymentRequest,
        amountSats,
      });

      setPreparedPayment(prepareResponse);
      console.log('prepareResponse=>', prepareResponse);
      if (
        prepareResponse.paymentMethod instanceof SendPaymentMethod.Bolt11Invoice
      ) {
        const lightningFee =
          prepareResponse.paymentMethod.inner.lightningFeeSats;
        const sparkFee =
          prepareResponse.paymentMethod.inner.sparkTransferFeeSats;
        console.log('lightningFee=:', lightningFee);
        console.log('sparkFee=:', sparkFee);
        Alert.alert(
          'Payment Prepared',
          `Lightning Fees: ${lightningFee} sats\nSpark Fees: ${
            sparkFee || 'N/A'
          } sats`,
          [
            {text: 'Cancel', style: 'cancel'},
            {text: 'Send Payment', onPress: () => sendPayment(prepareResponse)},
          ],
        );
      }
    } catch (err) {
      console.error('Error preparing payment:', err);
      Alert.alert('Prepare Error', err.message);
    }
  }

  async function sendPayment(prepareResponse) {
    if (!sdk) return;

    try {
      const response = await sdk.sendPayment({
        prepareResponse,
      });

      console.log('Payment sent:', response);
      Alert.alert('Success', 'Payment sent successfully!');

      // Clear form and refresh
      setPaymentRequest('');
      setSendAmount('');
      setPreparedPayment(null);
      await fetchBalance();
      await fetchPayments();
    } catch (err) {
      console.error('Error sending payment:', err);
      Alert.alert('Send Error', err.message);
    }
  }

  useEffect(() => {
    return () => {
      if (sdk && listenerId) {
        sdk.removeEventListener(listenerId);
      }
    };
  }, [sdk, listenerId]);

  console.log('receiveFeeSats=>', receiveFeeSats);
  return (
    <SafeAreaView style={styles.container}>
      <ScrollView style={styles.scrollView}>
        {/* Connection Section */}
        <View style={styles.section}>
          <TouchableOpacity
            onPress={connectToSdk}
            style={[styles.button, sdk && styles.buttonConnected]}
            disabled={!!sdk}>
            <Text style={styles.buttonText}>
              {sdk ? '✅ Connected' : 'Connect to Breez SDK with mnemonic'}
              {'\n'}
              {'\n'}
              {
                'mix budget despair always cancel actress inspire curtain large grape thing snack'
              }
            </Text>
          </TouchableOpacity>

          {balance !== null && (
            <View style={styles.balanceContainer}>
              <Text style={styles.balanceLabel}>Balance:</Text>
              <Text style={styles.balanceAmount}>
                {balance.toString()} sats
              </Text>
              <TouchableOpacity
                onPress={() => fetchBalance()}
                style={styles.refreshButton}>
                <Text>🔄 Refresh</Text>
              </TouchableOpacity>
            </View>
          )}
        </View>

        {/* Receive Payment Section */}
        {sdk && (
          <View style={styles.section}>
            <Text style={styles.sectionTitle}>Receive Payment</Text>
            <TextInput
              style={styles.input}
              placeholder="Invoice description"
              placeholderTextColor="#9CA3AF"
              value={invoiceDescription}
              onChangeText={setInvoiceDescription}
            />
            <TextInput
              style={styles.input}
              placeholder="Amount (sats) - optional"
              placeholderTextColor="#9CA3AF"
              value={invoiceAmount}
              onChangeText={setInvoiceAmount}
              keyboardType="number-pad"
            />
            <TouchableOpacity onPress={generateInvoice} style={styles.button}>
              <Text style={styles.buttonText}>Generate Invoice</Text>
            </TouchableOpacity>

            {generatedInvoice && (
              <View style={styles.invoiceContainer}>
                <Text style={styles.label}>Invoice:</Text>
                <Text style={styles.invoice} selectable>
                  {generatedInvoice}
                </Text>
                {/* {receiveFeeSats !== null && (
                  <Text style={styles.feeText}>
                    Fee: {receiveFeeSats.toString()} sats
                  </Text>
                )} */}
              </View>
            )}
          </View>
        )}

        {/* Send Payment Section */}
        {sdk && (
          <View style={styles.section}>
            <Text style={styles.sectionTitle}>Send Payment</Text>
            <TextInput
              style={[styles.input, styles.textArea]}
              placeholder="Paste Lightning invoice (bolt11)"
              placeholderTextColor="#9CA3AF"
              value={paymentRequest}
              onChangeText={setPaymentRequest}
              multiline
            />
            <TextInput
              style={styles.input}
              placeholder="Amount (sats) - optional"
              placeholderTextColor="#9CA3AF" // gray, change as needed
              value={sendAmount}
              onChangeText={setSendAmount}
              keyboardType="number-pad"
            />
            <TouchableOpacity onPress={preparePayment} style={styles.button}>
              <Text style={styles.buttonText}>Prepare & Send Payment</Text>
            </TouchableOpacity>
          </View>
        )}

        {/* Events Section */}
        {events.length > 0 && (
          <View style={styles.section}>
            <Text style={styles.sectionTitle}>
              Recent Events ({events.length})
            </Text>
            {events.map((item, idx) => (
              <View key={idx} style={styles.eventItem}>
                <Text style={styles.eventTime}>{item.timestamp}</Text>
                <Text style={styles.eventText}>
                  {JSON.stringify(item.event, null, 2)}
                </Text>
              </View>
            ))}
          </View>
        )}

        {/* Payments History */}
        {payments.length > 0 && (
          <View style={styles.section}>
            <Text style={styles.sectionTitle}>
              Payment History ({payments.length})
            </Text>
            {payments.slice(0, 5).map((payment, idx) => (
              <View key={idx} style={styles.paymentItem}>
                <Text style={styles.paymentAmount}>
                  {payment.amountSats?.toString() || 'N/A'} sats
                </Text>
                <Text style={styles.paymentStatus}>{payment.status}</Text>
              </View>
            ))}
          </View>
        )}
      </ScrollView>
    </SafeAreaView>
  );
}
function BreezSpark2() {
  const [sdk, setSdk] = useState(null);
  const [balance, setBalance] = useState(null);
  const [events, setEvents] = useState([]);
  const [listenerId, setListenerId] = useState(null);
  const [payments, setPayments] = useState([]);

  // For receiving payments
  const [invoiceDescription, setInvoiceDescription] = useState('');
  const [invoiceAmount, setInvoiceAmount] = useState('');
  const [generatedInvoice, setGeneratedInvoice] = useState('');
  const [receiveFeeSats, setReceiveFeeSats] = useState(null);

  // For sending payments
  const [paymentRequest, setPaymentRequest] = useState('');
  const [sendAmount, setSendAmount] = useState('');
  const [preparedPayment, setPreparedPayment] = useState(null);

  async function connectToSdk() {
    try {
      const mnemonic =''; // 9901 sats
      // const mnemonic = ''; //    10001
      let config = defaultConfig(Network.Regtest);
      config.apiKey ='';

      const baseDir = DocumentDirectoryPath.replace('file://', '');
      const workingDir = `${baseDir}breezSdkSpark`;

      //   const directoryPath = await getOrCreateDirectory('testing', workingDir);

      console.log('API Key configured:', !!config.apiKey);

      const seed = new Seed.Mnemonic({mnemonic});
      const connectedSdk = await connect({
        config,
        seed,
        storageDir: workingDir,
      });

      setSdk(connectedSdk);
      console.log('✅ Connected successfully!');

      // Start listening to events
      setupEventListener(connectedSdk);

      // Get initial balance
      await fetchBalance(connectedSdk);

      // Load payment history
      await fetchPayments(connectedSdk);
    } catch (err) {
      console.error('❌ Connection error:', err);
      Alert.alert('Connection Error', err.message);
    }
  }

  function setupEventListener(sdkInstance) {
    const eventListener = new JsEventListener(event => {
      setEvents(prev =>
        [{event, timestamp: new Date().toISOString()}, ...prev].slice(0, 10),
      );

      // Refresh balance on payment events
      if (
        event.type === 'paymentSucceeded' ||
        event.type === 'paymentReceived'
      ) {
        fetchBalance(sdkInstance);
        fetchPayments(sdkInstance);
      }
    });

    const id = sdkInstance.addEventListener(eventListener);
    setListenerId(id);
    console.log('Event listener registered:', id);
  }

  async function fetchBalance(sdkInstance = sdk) {
    if (!sdkInstance) return;

    try {
      const info = await sdkInstance.getInfo({});
      setBalance(info.balanceSats);
      console.log('Balance:', info.balanceSats);
    } catch (err) {
      console.error('Error fetching balance:', err);
      Alert.alert('Balance Error', err.message);
    }
  }

  async function fetchPayments(sdkInstance = sdk) {
    if (!sdkInstance) return;

    try {
      const response = await sdkInstance.listPayments({
        offset: undefined,
        limit: 20,
      });
      console.log('response=>', response);
      setPayments(response.payments);
      console.log('Payments loaded:', response.payments.length);
    } catch (err) {
      console.error('Error fetching payments:', err);
    }
  }

  async function generateInvoice() {
    if (!sdk) {
      Alert.alert('Error', 'SDK not connected');
      return;
    }

    try {
      const amountSats = invoiceAmount ? BigInt(invoiceAmount) : undefined;

      // NOTE vai invoice
      const response = await sdk.receivePayment({
        paymentMethod: new ReceivePaymentMethod.Bolt11Invoice({
          description: invoiceDescription || 'Payment',
          amountSats,
        }),
      });
      console.log('response=>', response);
      // NOTE vai btc address ub regtest
      // const response = await sdk.receivePayment({
      //   paymentMethod: new ReceivePaymentMethod.BitcoinAddress()
      // })

      console.log(`Payment Request: ${paymentRequest}`);
      setGeneratedInvoice(response.paymentRequest);
      setReceiveFeeSats(response.fee);
      console.log('Invoice generated:', response.paymentRequest);
      console.log('Receive fees:', response.fee);

      Alert.alert('Invoice Generated', 'Check the invoice field below');
    } catch (err) {
      console.error('Error generating invoice:', err);
      Alert.alert('Invoice Error', err.message);
    }
  }

  async function preparePayment() {
    if (!sdk || !paymentRequest) {
      Alert.alert('Error', 'SDK not connected or no payment request');
      return;
    }

    try {
      const amountSats = sendAmount ? BigInt(sendAmount) : undefined;
      console.log('paymentRequest: ', paymentRequest);
      console.log('amountSats: ', amountSats);
      const prepareResponse = await sdk.prepareSendPayment({
        paymentRequest,
        amountSats,
      });

      setPreparedPayment(prepareResponse);

      if (
        prepareResponse.paymentMethod instanceof SendPaymentMethod.Bolt11Invoice
      ) {
        const lightningFee =
          prepareResponse.paymentMethod.inner.lightningFeeSats;
        const sparkFee =
          prepareResponse.paymentMethod.inner.sparkTransferFeeSats;
        console.log('lightningFee=:', lightningFee);
        console.log('sparkFee=:', sparkFee);
        // Alert.alert(
        //   'Payment Prepared',
        //   `Lightning Fees: ${lightningFee} sats\nSpark Fees: ${
        //     sparkFee || 'N/A'
        //   } sats`,
        //   [
        //     {text: 'Cancel', style: 'cancel'},
        //     {text: 'Send Payment', onPress: () => sendPayment(prepareResponse)},
        //   ],
        // );
      }
    } catch (err) {
      console.error('Error preparing payment:', err);
      Alert.alert('Prepare Error', err.message);
    }
  }

  async function sendPayment(prepareResponse) {
    if (!sdk) return;

    try {
      const response = await sdk.sendPayment({
        prepareResponse,
      });

      console.log('Payment sent:', response);
      Alert.alert('Success', 'Payment sent successfully!');

      // Clear form and refresh
      setPaymentRequest('');
      setSendAmount('');
      setPreparedPayment(null);
      await fetchBalance();
      await fetchPayments();
    } catch (err) {
      console.error('Error sending payment:', err);
      Alert.alert('Send Error', err.message);
    }
  }

  useEffect(() => {
    return () => {
      if (sdk && listenerId) {
        sdk.removeEventListener(listenerId);
      }
    };
  }, [sdk, listenerId]);

  console.log('receiveFeeSats=>', receiveFeeSats);
  return (
    <SafeAreaView style={styles.container}>
      <ScrollView style={styles.scrollView}>
        {/* Connection Section */}
        <View style={styles.section}>
          <TouchableOpacity
            onPress={connectToSdk}
            style={[styles.button, sdk && styles.buttonConnected]}
            disabled={!!sdk}>
            <Text style={styles.buttonText}>
              {sdk ? '✅ Connected' : 'Connect to Breez SDK'}
              {'\n'}
              {
                'soon provide struggle fitness dream emotion test cook wrestle price extra vapor'
              }
            </Text>
          </TouchableOpacity>

          {balance !== null && (
            <View style={styles.balanceContainer}>
              <Text style={styles.balanceLabel}>Balance:</Text>
              <Text style={styles.balanceAmount}>
                {balance.toString()} sats
              </Text>
              <TouchableOpacity
                onPress={() => fetchBalance()}
                style={styles.refreshButton}>
                <Text>🔄 Refresh</Text>
              </TouchableOpacity>
            </View>
          )}
        </View>

        {/* Receive Payment Section */}
        {sdk && (
          <View style={styles.section}>
            <Text style={styles.sectionTitle}>Receive Payment</Text>
            <TextInput
              style={styles.input}
              placeholder="Invoice description"
              placeholderTextColor="#9CA3AF"
              value={invoiceDescription}
              onChangeText={setInvoiceDescription}
            />
            <TextInput
              style={styles.input}
              placeholder="Amount (sats) - optional"
              placeholderTextColor="#9CA3AF"
              value={invoiceAmount}
              onChangeText={setInvoiceAmount}
              keyboardType="number-pad"
            />
            <TouchableOpacity onPress={generateInvoice} style={styles.button}>
              <Text style={styles.buttonText}>Generate Invoice</Text>
            </TouchableOpacity>

            {generatedInvoice && (
              <View style={styles.invoiceContainer}>
                <Text style={styles.label}>Invoice:</Text>
                <Text style={styles.invoice} selectable>
                  {generatedInvoice}
                </Text>
                {/* {receiveFeeSats !== null && (
                  <Text style={styles.feeText}>
                    Fee: {receiveFeeSats.toString()} sats
                  </Text>
                )} */}
              </View>
            )}
          </View>
        )}

        {/* Send Payment Section */}
        {sdk && (
          <View style={styles.section}>
            <Text style={styles.sectionTitle}>Send Payment</Text>
            <TextInput
              style={[styles.input, styles.textArea]}
              placeholder="Paste Lightning invoice (bolt11)"
              placeholderTextColor="#9CA3AF"
              value={paymentRequest}
              onChangeText={setPaymentRequest}
              multiline
            />
            <TextInput
              style={styles.input}
              placeholder="Amount (sats) - optional"
              placeholderTextColor="#9CA3AF" // gray, change as needed
              value={sendAmount}
              onChangeText={setSendAmount}
              keyboardType="number-pad"
            />
            <TouchableOpacity onPress={preparePayment} style={styles.button}>
              <Text style={styles.buttonText}>Prepare & Send Payment</Text>
            </TouchableOpacity>
          </View>
        )}

        {/* Events Section */}
        {events.length > 0 && (
          <View style={styles.section}>
            <Text style={styles.sectionTitle}>
              Recent Events ({events.length})
            </Text>
            {events.map((item, idx) => (
              <View key={idx} style={styles.eventItem}>
                <Text style={styles.eventTime}>{item.timestamp}</Text>
                <Text style={styles.eventText}>
                  {JSON.stringify(item.event, null, 2)}
                </Text>
              </View>
            ))}
          </View>
        )}

        {/* Payments History */}
        {payments.length > 0 && (
          <View style={styles.section}>
            <Text style={styles.sectionTitle}>
              Payment History ({payments.length})
            </Text>
            {payments.slice(0, 5).map((payment, idx) => (
              <View key={idx} style={styles.paymentItem}>
                <Text style={styles.paymentAmount}>
                  {payment.amountSats?.toString() || 'N/A'} sats
                </Text>
                <Text style={styles.paymentStatus}>{payment.status}</Text>
              </View>
            ))}
          </View>
        )}
      </ScrollView>
    </SafeAreaView>
  );
}
const renderScene = SceneMap({
  add_coins: BreezSpark1,
  add_coins_group: BreezSpark2,
});

export default function BreezSpark() {
  const [routes] = React.useState(ROUTES);
  const [index, setIndex] = React.useState(0);
  return (
    <View style={{flex: 1, marginTop: 50}}>
      <TabView
        navigationState={{index, routes}}
        renderScene={renderScene}
        onIndexChange={setIndex}
        initialLayout={{width: SCREEN_WIDTH}}
        renderTabBar={props => <RenderTabBar {...props} styles={styles} />}
        lazy={true}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  scrollView: {
    flex: 1,
    padding: 16,
  },
  section: {
    backgroundColor: 'white',
    borderRadius: 12,
    padding: 16,
    marginBottom: 16,
    shadowColor: '#000',
    shadowOffset: {width: 0, height: 2},
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 12,
    color: '#333',
  },
  button: {
    backgroundColor: '#007AFF',
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
  },
  buttonConnected: {
    backgroundColor: '#34C759',
  },
  buttonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: '600',
  },
  balanceContainer: {
    marginTop: 16,
    padding: 16,
    backgroundColor: '#f8f8f8',
    borderRadius: 8,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  balanceLabel: {
    fontSize: 16,
    color: '#666',
  },
  balanceAmount: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#007AFF',
    flex: 1,
    marginLeft: 8,
  },
  refreshButton: {
    padding: 8,
  },
  input: {
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 8,
    padding: 12,
    marginBottom: 12,
    fontSize: 16,
  },
  textArea: {
    height: 80,
    textAlignVertical: 'top',
  },
  invoiceContainer: {
    marginTop: 12,
    padding: 12,
    backgroundColor: '#f8f8f8',
    borderRadius: 8,
  },
  label: {
    fontSize: 14,
    fontWeight: '600',
    marginBottom: 8,
    color: '#666',
  },
  invoice: {
    fontSize: 12,
    color: '#333',
    fontFamily: 'monospace',
  },
  feeText: {
    marginTop: 8,
    fontSize: 14,
    color: '#666',
  },
  eventItem: {
    padding: 12,
    backgroundColor: '#f8f8f8',
    borderRadius: 8,
    marginBottom: 8,
  },
  eventTime: {
    fontSize: 12,
    color: '#666',
    marginBottom: 4,
  },
  eventText: {
    fontSize: 12,
    fontFamily: 'monospace',
    color: '#333',
  },
  paymentItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    padding: 12,
    backgroundColor: '#f8f8f8',
    borderRadius: 8,
    marginBottom: 8,
  },
  paymentAmount: {
    fontSize: 16,
    fontWeight: '600',
    color: '#333',
  },
  paymentStatus: {
    fontSize: 14,
    color: '#666',
  },
});

Enter fullscreen mode Exit fullscreen mode

Top comments (0)