LnBits integration

This commit is contained in:
KoalaSat 2023-03-22 15:37:13 +01:00
parent f95a62be52
commit ee62d25340
No known key found for this signature in database
GPG Key ID: 2F7F61C6146AB157
15 changed files with 641 additions and 171 deletions

View File

@ -25,7 +25,7 @@ export const MenuItems: React.FC = () => {
const [drawerItemIndex, setDrawerItemIndex] = React.useState<number>(-1)
const { getSatoshiSymbol } = React.useContext(AppContext)
const { relays } = React.useContext(RelayPoolContext)
const { balance, active } = React.useContext(WalletContext)
const { balance, type } = React.useContext(WalletContext)
const {
nPub,
publicKey,
@ -154,7 +154,7 @@ export const MenuItems: React.FC = () => {
onPress={() => onPressItem('wallet', 1)}
onTouchEnd={() => setDrawerItemIndex(-1)}
right={() => {
if (!active) return <></>
if (!type || !balance) return <></>
return (
<Text>
{`${balance} `}

View File

@ -1,31 +1,20 @@
import axios from 'axios'
import { getUnixTime } from 'date-fns'
import React, { useEffect, useState } from 'react'
import SInfo from 'react-native-sensitive-info'
export interface LndHub {
accessToken: string
refreshToken: string
url: string
}
export interface WalletAction {
id: string
monto: number
type: 'invoice' | 'transaction'
description: string
timestamp: number
}
import type WalletAction from '../lib/Lightning'
import LnBits, { type LnBitsConfig } from '../lib/Lightning/LnBits'
import LndHub, { type LndHubConfig } from '../lib/Lightning/LndHub'
export interface WalletContextProps {
active: boolean
updatedAt?: number
lndHub?: LndHub
setLndHub: (lndHub: LndHub) => void
type?: string
setType: (type: string) => void
updatedAt?: string
lndHub?: LndHubConfig | LnBitsConfig
balance?: number
transactions: WalletAction[]
invoices: WalletAction[]
refreshLndHub: (login?: string, password?: string, uri?: string) => void
updateWallet: () => Promise<void>
refreshWallet: (params?: object, type?: string) => void
payInvoice: (invoice: string) => Promise<boolean>
logoutWallet: () => void
}
@ -35,18 +24,18 @@ export interface WalletContextProviderProps {
}
export const initialWalletContext: WalletContextProps = {
active: false,
setLndHub: () => {},
transactions: [],
invoices: [],
refreshLndHub: () => {},
setType: () => {},
updateWallet: async () => {},
refreshWallet: () => {},
payInvoice: async () => false,
logoutWallet: () => {},
}
export const WalletContextProvider = ({ children }: WalletContextProviderProps): JSX.Element => {
const [active, setActive] = React.useState<boolean>(initialWalletContext.active)
const [lndHub, setLndHub] = useState<LndHub>()
const [type, setType] = React.useState<string>()
const [config, setConfig] = useState<LndHubConfig | LnBitsConfig>()
const [balance, setBalance] = useState<number>()
const [updatedAt, setUpdatedAt] = useState<string>()
const [transactions, setTransactions] = useState<WalletAction[]>(
@ -57,119 +46,82 @@ export const WalletContextProvider = ({ children }: WalletContextProviderProps):
useEffect(() => {
SInfo.getItem('lndHub', {}).then((value) => {
if (value) {
setLndHub(JSON.parse(value))
setConfig(JSON.parse(value))
setType('lndHub')
}
})
SInfo.getItem('lnBits', {}).then((value) => {
if (value) {
setConfig(JSON.parse(value))
setType('lnBits')
}
})
}, [])
const refreshLndHub: (login?: string, password?: string, uri?: string) => void = (
login,
password,
uri,
const getClient: (params?: any, clientType?: string) => LndHub | LnBits | undefined = (
params,
clientType,
) => {
setLndHub(undefined)
let params:
| { type: string; refresh_token?: string; login?: string; password?: string }
| undefined
if (lndHub?.refreshToken) {
params = {
type: 'refresh_token',
refresh_token: lndHub?.refreshToken,
}
uri = lndHub?.url
} else if (login !== '' && password !== '' && uri !== '') {
params = {
type: 'auth',
login,
password,
const kind = clientType ?? type
let client
if (kind === 'lndHub') {
client = new LndHub(params ?? config)
} else if (kind === 'lnBits') {
client = new LnBits(params ?? config)
}
return client
}
const refreshWallet: (params?: any, clientType?: string) => void = (params, clientType) => {
setConfig(undefined)
if (clientType) {
setType(clientType)
const client = getClient(params, clientType)
if (client) {
client.refresh(params).then((response) => {
if (response?.status === 200) setConfig(response.config)
})
}
}
if (params && uri) {
axios.post(`${uri}/auth`, {}, { params }).then((response) => {
if (response?.data?.refresh_token && response.data?.access_token && uri) {
setLndHub({
accessToken: response.data.access_token,
refreshToken: response.data.refresh_token,
url: uri,
})
}
const updateWallet: () => Promise<void> = async () => {
console.log('config', config)
console.log('type', type)
if (!config || !type) return
const client = getClient()
console.log('client', client)
if (client) {
client.getBalance().then((response) => {
if (response?.status === 200) {
setUpdatedAt(`${getUnixTime(new Date())}-balance`)
setBalance(response.balance)
SInfo.setItem(type, JSON.stringify(client.config), {})
} else if (response?.status === 401) {
refreshWallet()
}
})
client.getMovements(setTransactions, setInvoices, setUpdatedAt)
}
}
const updateLndHub: () => void = () => {
if (!lndHub) return
const headers = {
Authorization: `Bearer ${lndHub.accessToken}`,
}
axios.get(`${lndHub.url}/balance`, { headers }).then((response) => {
if (response) {
if (response.status === 200) {
setUpdatedAt(`${getUnixTime(new Date())}-balance`)
setBalance(response.data?.BTC?.AvailableBalance ?? 0)
SInfo.setItem('lndHub', JSON.stringify(lndHub), {})
setActive(true)
} else if (response.status === 401) {
refreshLndHub()
}
}
})
axios.get(`${lndHub.url}/gettxs`, { headers }).then((response) => {
if (response) {
setTransactions(
response.data.map((item: any) => {
return {
id: item.payment_preimage,
monto: item.value,
type: 'transaction',
description: item.memo,
timestamp: item.timestamp,
}
}),
)
setUpdatedAt(`${getUnixTime(new Date())}-gettxs`)
}
})
axios.get(`${lndHub.url}/getuserinvoices`, { headers }).then((response) => {
if (response) {
setInvoices(
response.data
.filter((item: any) => item.ispaid)
.map((item: any) => {
return {
id: item.payment_hash,
monto: item.amt,
type: 'invoice',
description: item.description,
timestamp: item.timestamp,
}
}),
)
setUpdatedAt(`${getUnixTime(new Date())}-getuserinvoices`)
}
})
}
useEffect(updateLndHub, [lndHub])
useEffect(() => {
if (config) updateWallet()
}, [config])
const payInvoice: (invoice: string) => Promise<boolean> = async (invoice) => {
if (active && invoice && invoice !== '') {
const headers = {
Authorization: `Bearer ${lndHub?.accessToken}`,
}
const params = {
invoice,
}
const response = await axios.post(`${lndHub?.url}/payinvoice`, params, { headers })
if (response) {
if (response.status === 200) {
updateLndHub()
return response?.data?.payment_error === ''
} else if (response.status === 401) {
refreshLndHub()
}
if (type && config) {
const client = new LndHub(config as LndHubConfig)
const response = await client.payInvoice(invoice)
if (response?.status === 200) {
updateWallet()
return true
} else if (response?.status === 401) {
refreshWallet()
return true
}
}
@ -178,8 +130,8 @@ export const WalletContextProvider = ({ children }: WalletContextProviderProps):
const logoutWallet: () => void = () => {
SInfo.deleteItem('lndHub', {})
setActive(false)
setLndHub(undefined)
setType(undefined)
setConfig(undefined)
setBalance(undefined)
setUpdatedAt(undefined)
setTransactions([])
@ -189,13 +141,14 @@ export const WalletContextProvider = ({ children }: WalletContextProviderProps):
return (
<WalletContext.Provider
value={{
active,
type,
setType,
updatedAt,
setLndHub,
balance,
transactions,
invoices,
refreshLndHub,
refreshWallet,
updateWallet,
payInvoice,
logoutWallet,
}}

View File

@ -26,6 +26,17 @@
"loginStep3Description": "You can add the relays used by your contacts to your list and connect to them to strengthen the network.",
"loginskip": "You can skip this process but you may miss out on the opportunity to connect to a diverse range of relays and expand your network's reach"
},
"walletPage": {
"addLnBits": "Add LnBits",
"adminKey": "Admin Key",
"invoiceReadKey": "Invoice Read Key",
"address": "Address",
"walletId": "Wallet id",
"connectingWallet": "Connecting to wallet",
"lnHub": "LNDHub address",
"addLnhub": "Add LNDHub",
"connect": "Connect"
},
"homeNavigator": {
"ProfileCreate": "Profil anlegen",
"Search": "",

View File

@ -27,8 +27,14 @@
"loginskip": "You can skip this process but you may miss out on the opportunity to connect to a diverse range of relays and expand your network's reach"
},
"walletPage": {
"addLnhub": "Add LNHub",
"lnHub": "LNHub address",
"addLnBits": "Add LnBits",
"adminKey": "Admin Key",
"invoiceReadKey": "Invoice Read Key",
"address": "Address",
"walletId": "Wallet id",
"connectingWallet": "Connecting to wallet",
"lnHub": "LNDHub address",
"addLnhub": "Add LNDHub",
"connect": "Connect"
},
"homeNavigator": {

View File

@ -26,6 +26,17 @@
"loginStep3Description": "You can add the relays used by your contacts to your list and connect to them to strengthen the network.",
"loginskip": "You can skip this process but you may miss out on the opportunity to connect to a diverse range of relays and expand your network's reach"
},
"walletPage": {
"addLnBits": "Añadir LnBits",
"adminKey": "Admin Key",
"invoiceReadKey": "Invoice Read Key",
"address": "Dirección",
"walletId": "Wallet id",
"connectingWallet": "Conectando a la wallet",
"lnHub": "Dirección LNDHub",
"addLnhub": "Añadir LNDHub",
"connect": "Connectar"
},
"searchPage": {
"placeholder": "Busca for claves públicas, notas, ...",
"emptyTitle": "Consejo",

View File

@ -26,6 +26,17 @@
"loginStep3Description": "You can add the relays used by your contacts to your list and connect to them to strengthen the network.",
"loginskip": "You can skip this process but you may miss out on the opportunity to connect to a diverse range of relays and expand your network's reach"
},
"walletPage": {
"addLnBits": "Add LnBits",
"adminKey": "Admin Key",
"invoiceReadKey": "Invoice Read Key",
"address": "Address",
"walletId": "Wallet id",
"connectingWallet": "Connecting to wallet",
"lnHub": "LNDHub address",
"addLnhub": "Add LNDHub",
"connect": "Connect"
},
"searchPage": {
"placeholder": "Look for public keys, notes, hashtags...",
"emptyTitle": "Tip",

View File

@ -26,6 +26,17 @@
"loginStep3Description": "You can add the relays used by your contacts to your list and connect to them to strengthen the network.",
"loginskip": "You can skip this process but you may miss out on the opportunity to connect to a diverse range of relays and expand your network's reach"
},
"walletPage": {
"addLnBits": "Add LnBits",
"adminKey": "Admin Key",
"invoiceReadKey": "Invoice Read Key",
"address": "Address",
"walletId": "Wallet id",
"connectingWallet": "Connecting to wallet",
"lnHub": "LNDHub address",
"addLnhub": "Add LNDHub",
"connect": "Connect"
},
"searchPage": {
"placeholder": "Look for public keys, notes, hashtags...",
"emptyTitle": "Tip",

View File

@ -26,6 +26,17 @@
"loginStep3Description": "您可以将您的联系人使用的中继添加到您的列表中并连接到它们以加强网络",
"loginskip": "您可以跳过此过程,但您可能会错过连接到各种中继和扩大网络覆盖范围的机会"
},
"walletPage": {
"addLnBits": "Add LnBits",
"adminKey": "Admin Key",
"invoiceReadKey": "Invoice Read Key",
"address": "Address",
"walletId": "Wallet id",
"connectingWallet": "Connecting to wallet",
"lnHub": "LNDHub address",
"addLnhub": "Add LNDHub",
"connect": "Connect"
},
"searchPage": {
"placeholder": "查找公钥Notes标签...",
"emptyTitle": "提示",

View File

@ -32,7 +32,7 @@ export const NotificationsFeed: React.FC = () => {
const theme = useTheme()
const { t } = useTranslation('common')
const { database, setNotificationSeenAt, pushedTab, getSatoshiSymbol } = useContext(AppContext)
const { publicKey, reloadLists, mutedEvents } = useContext(UserContext)
const { publicKey, reloadLists, mutedEvents, mutedUsers } = useContext(UserContext)
const { lastEventId, relayPool } = useContext(RelayPoolContext)
const [pubKeys, setPubKeys] = React.useState<string[]>([])
const [users, setUsers] = React.useState<User[]>([])
@ -145,7 +145,9 @@ export const NotificationsFeed: React.FC = () => {
const unmutedThreads = notes.filter((note) => {
if (!note?.id) return false
const eTags = getETags(note)
return !eTags.some((tag) => mutedEvents.includes(tag[1]))
return (
!eTags.some((tag) => mutedEvents.includes(tag[1])) && !mutedUsers.includes(note.pubkey)
)
})
setMentionNotes(unmutedThreads)
setRefreshing(false)

View File

@ -1,4 +1,5 @@
import Clipboard from '@react-native-clipboard/clipboard'
import { useFocusEffect } from '@react-navigation/native'
import { differenceInDays, format, fromUnixTime, isSameDay } from 'date-fns'
import { t } from 'i18next'
import React, { useEffect, useMemo } from 'react'
@ -15,24 +16,36 @@ import {
} from 'react-native-paper'
import RBSheet from 'react-native-raw-bottom-sheet'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
import Logo from '../../Components/Logo'
import NostrosAvatar from '../../Components/NostrosAvatar'
import { AppContext } from '../../Contexts/AppContext'
import { type WalletAction, WalletContext } from '../../Contexts/WalletContext'
import { WalletContext } from '../../Contexts/WalletContext'
import { getZaps, type Zap } from '../../Functions/DatabaseFunctions/Zaps'
import type WalletAction from '../../lib/Lightning'
import { navigate } from '../../lib/Navigation'
export const WalletPage: React.FC = () => {
const theme = useTheme()
const { getSatoshiSymbol, database, setDisplayUserDrawer } = React.useContext(AppContext)
const { refreshLndHub, active, balance, transactions, invoices, updatedAt } =
const { refreshWallet, updateWallet, type, balance, transactions, invoices, updatedAt } =
React.useContext(WalletContext)
const [lnHubAddress, setLndHubAddress] = React.useState<string>()
const [lnBitsAddress, setLnBitsAddress] = React.useState<string>()
const [lnBitsInvoiceKey, setLnBitsInvoiceKey] = React.useState<string>()
const [lnBitsAdminKey, setLnBitsAdminKey] = React.useState<string>()
const [showNotification, setShowNotification] = React.useState<undefined | string>()
const [actions, setActions] = React.useState<WalletAction[]>([])
const [zaps, setZaps] = React.useState<Record<string, Zap>>({})
const bottomLndHubRef = React.useRef<RBSheet>(null)
const bottomLndBitsRef = React.useRef<RBSheet>(null)
useFocusEffect(
React.useCallback(() => {
updateWallet()
return () => {}
}, []),
)
useEffect(refreshLndHub, [])
useEffect(() => {
const array = [...transactions, ...invoices].sort(
(item1, item2) => item2.timestamp - item1.timestamp,
@ -51,9 +64,9 @@ export const WalletPage: React.FC = () => {
}
}, [updatedAt])
const pasteLndHub: () => void = () => {
const paste: (setFunction: (value: string) => void) => void = (setFunction) => {
Clipboard.getString().then((value) => {
setLndHubAddress(value ?? '')
setFunction(value ?? '')
})
}
@ -67,13 +80,27 @@ export const WalletPage: React.FC = () => {
if (uri[uri.length - 1] === '/') {
uri = uri.substring(0, uri.length - 1)
}
refreshLndHub(login, password, uri)
refreshWallet({ login, password, uri }, 'lndHub')
setLndHubAddress(undefined)
bottomLndHubRef.current?.close()
}
}
}
const connectLnBits: () => void = () => {
if (lnBitsAddress && lnBitsAdminKey && lnBitsInvoiceKey) {
let uri = lnBitsAddress
if (uri[uri.length - 1] === '/') {
uri = uri.substring(0, uri.length - 1)
}
refreshWallet({ lnBitsAddress, lnBitsAdminKey, lnBitsInvoiceKey }, 'lnBits')
setLnBitsAddress(undefined)
setLnBitsAdminKey(undefined)
setLnBitsInvoiceKey(undefined)
bottomLndBitsRef.current?.close()
}
}
const bottomSheetStyles = React.useMemo(() => {
return {
container: {
@ -95,6 +122,13 @@ export const WalletPage: React.FC = () => {
<Button mode='contained' onPress={() => bottomLndHubRef.current?.open()}>
{t('walletPage.addLnhub')}
</Button>
<Button
mode='contained'
style={styles.button}
onPress={() => bottomLndBitsRef.current?.open()}
>
{t('walletPage.addLnBits')}
</Button>
</View>
),
[],
@ -109,7 +143,7 @@ export const WalletPage: React.FC = () => {
const zap = zaps[item.id]
return (
<>
<View key={item.id ?? index}>
{(index === 0 || !isSameDay(date, prevDate)) && (
<Text variant='titleMedium'>{format(date, formatPattern)}</Text>
)}
@ -172,29 +206,33 @@ export const WalletPage: React.FC = () => {
</View>
</View>
</TouchableRipple>
</>
</View>
)
}
return (
<View style={styles.container}>
{active ? (
<View>
<View style={[styles.balance, { backgroundColor: theme.colors.onSecondary }]}>
<View style={styles.balanceNumber}>
<Text variant='displayMedium'>{`${balance} `}</Text>
<Text style={styles.balanceSymbol} variant='headlineSmall'>
{getSatoshiSymbol()}
</Text>
{type ? (
balance !== undefined ? (
<View>
<View style={[styles.balance, { backgroundColor: theme.colors.onSecondary }]}>
<View style={styles.balanceNumber}>
<Text variant='displayMedium'>{`${balance} `}</Text>
<Text style={styles.balanceSymbol} variant='headlineSmall'>
{getSatoshiSymbol()}
</Text>
</View>
</View>
<FlatList data={actions} renderItem={renderAction} style={styles.list} />
</View>
<FlatList
data={actions}
renderItem={renderAction}
style={styles.list}
keyExtractor={(item) => item.id}
/>
</View>
) : (
<View style={styles.center}>
<View style={styles.centerItem}>
<Logo onlyIcon size='large' />
</View>
<Text style={styles.centerItem}>{t('walletPage.connectingWallet')}</Text>
</View>
)
) : (
login
)}
@ -206,7 +244,7 @@ export const WalletPage: React.FC = () => {
onIconPress={() => setShowNotification(undefined)}
onDismiss={() => setShowNotification(undefined)}
>
{t(`profileCard.notifications.${showNotification}`)}
{t(`walletPage.notifications.${showNotification}`)}
</Snackbar>
)}
<RBSheet ref={bottomLndHubRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
@ -224,7 +262,7 @@ export const WalletPage: React.FC = () => {
right={
<TextInput.Icon
icon='content-paste'
onPress={pasteLndHub}
onPress={() => paste(setLndHubAddress)}
forceTextInputFocus={false}
/>
}
@ -234,6 +272,69 @@ export const WalletPage: React.FC = () => {
</Button>
</View>
</RBSheet>
<RBSheet ref={bottomLndBitsRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
<View>
<Text variant='headlineSmall' style={styles.drawerParagraph}>
{t('walletPage.addLnBits')}
</Text>
<TextInput
style={styles.drawerParagraph}
mode='outlined'
multiline
label={t('walletPage.address') ?? ''}
onChangeText={setLnBitsAddress}
value={lnBitsAddress}
right={
<TextInput.Icon
icon='content-paste'
onPress={() => paste(setLnBitsAddress)}
forceTextInputFocus={false}
/>
}
/>
<TextInput
style={styles.drawerParagraph}
mode='outlined'
multiline
label={t('walletPage.adminKey') ?? ''}
onChangeText={setLnBitsAdminKey}
value={lnBitsAdminKey}
right={
<TextInput.Icon
icon='content-paste'
onPress={() => paste(setLnBitsAdminKey)}
forceTextInputFocus={false}
/>
}
/>
<TextInput
style={styles.drawerParagraph}
mode='outlined'
multiline
label={t('walletPage.invoiceReadKey') ?? ''}
onChangeText={setLnBitsInvoiceKey}
value={lnBitsInvoiceKey}
right={
<TextInput.Icon
icon='content-paste'
onPress={() => paste(setLnBitsInvoiceKey)}
forceTextInputFocus={false}
/>
}
/>
<Button
mode='contained'
onPress={connectLnBits}
disabled={
lnBitsAddress === undefined ||
lnBitsAdminKey === undefined ||
lnBitsInvoiceKey === undefined
}
>
{t('walletPage.connect')}
</Button>
</View>
</RBSheet>
</View>
)
}
@ -245,10 +346,18 @@ const styles = StyleSheet.create({
},
center: {
justifyContent: 'center',
alignContent: 'center',
height: '100%',
padding: 16,
},
centerItem: {
flexDirection: 'row',
justifyContent: 'center',
width: '100%',
textAlign: 'center',
},
button: {
marginTop: 16,
},
drawerParagraph: {
marginBottom: 16,
},

View File

@ -0,0 +1,108 @@
import axios from 'axios'
import { getUnixTime } from 'date-fns'
import type WalletAction from '..'
export interface LnBitsConfig {
lnBitsAddress: string
lnBitsAdminKey: string
lnBitsInvoiceKey: string
}
class LndHub {
constructor(config?: LnBitsConfig) {
this.config = config
}
public config: LnBitsConfig | undefined
private readonly getHeaders: () => object | undefined = () => {
if (!this.config) return
return {
'X-Api-Key': this.config.lnBitsInvoiceKey,
}
}
public payInvoice: (invoice: string) => Promise<{ status: number } | undefined> = async (
invoice,
) => {
if (!this.config) return
if (invoice && invoice !== '') {
const params = {
invoice,
}
const response = await axios.post(`${this.config?.lnBitsAddress}/payinvoice`, params, {
headers: this.getHeaders(),
})
if (response) {
return { status: response.status }
}
}
}
public getBalance: () => Promise<{ balance?: number; status: number } | undefined> = async () => {
if (!this.config) return
const headers = {
'X-Api-Key': this.config.lnBitsInvoiceKey,
}
const response = await axios.get(`${this.config.lnBitsAddress}/api/v1/wallet`, { headers })
if (response) {
return {
balance: (response.data?.balance ?? 0) / 1000,
status: response.status,
}
}
}
public getMovements: (
setTransactions: (transactions: WalletAction[]) => void,
setInvoices: (invoices: WalletAction[]) => void,
setUpdatedAt: (updatedAt: string) => void,
) => Promise<void> = async (setTransactions, setInvoices, setUpdatedAt) => {
if (!this.config) return
const headers = {
'X-Api-Key': this.config.lnBitsInvoiceKey,
}
const response = await axios.get(`${this.config.lnBitsAddress}/api/v1/payments`, { headers })
if (response) {
setTransactions(
response.data
.filter((item: any) => item.amount < 0)
.map((item: any) => {
return {
id: item.payment_preimage,
monto: Math.abs(item.amount / 1000),
type: 'transaction',
description: item.memo,
timestamp: item.time,
}
}),
)
setInvoices(
response.data
.filter((item: any) => item.amount > 0)
.map((item: any) => {
return {
id: item.payment_preimage,
monto: item.amount / 1000,
type: 'invoice',
description: item.memo,
timestamp: item.time,
}
}),
)
setUpdatedAt(`${getUnixTime(new Date())}-movements`)
}
}
public refresh: (params: any) => Promise<{ config: LnBitsConfig; status: number } | undefined> =
async (params) => {
if (params?.lnBitsAddress) {
return {
config: params,
status: 200,
}
}
}
}
export default LndHub

View File

@ -0,0 +1,134 @@
import axios from 'axios'
import { getUnixTime } from 'date-fns'
import type WalletAction from '..'
export interface LndHubConfig {
accessToken: string
refreshToken: string
url: string
}
class LndHub {
constructor(config?: LndHubConfig) {
this.config = config
}
public config: LndHubConfig | undefined
private readonly getHeaders: () => object | undefined = () => {
if (!this.config) return
return {
Authorization: `Bearer ${this.config.accessToken}`,
}
}
public payInvoice: (invoice: string) => Promise<{ status: number } | undefined> = async (
invoice,
) => {
if (!this.config) return
if (invoice && invoice !== '') {
const params = {
invoice,
}
const response = await axios.post(`${this.config?.url}/payinvoice`, params, {
headers: this.getHeaders(),
})
if (response) {
return { status: response.status }
}
}
}
public getBalance: () => Promise<{ balance?: number; status: number } | undefined> = async () => {
if (!this.config) return
const response = await axios.get(`${this.config.url}/balance`, { headers: this.getHeaders() })
if (response) {
return {
balance: response.data?.BTC?.AvailableBalance ?? 0,
status: response.status,
}
}
}
private readonly getTransactions: () => Promise<WalletAction[] | undefined> = async () => {
if (!this.config) return
const response = await axios.get(`${this.config.url}/gettxs`, { headers: this.getHeaders() })
if (response) {
return response.data.map((item: any) => {
return {
id: item.payment_preimage,
monto: item.value,
type: 'transaction',
description: item.memo,
timestamp: item.timestamp,
}
})
}
}
private readonly getInvoices: () => Promise<WalletAction[] | undefined> = async () => {
if (!this.config) return
const response = await axios.get(`${this.config.url}/getuserinvoices`, {
headers: this.getHeaders(),
})
if (response) {
return response.data
.filter((item: any) => item.ispaid)
.map((item: any) => {
return {
id: item.payment_hash,
monto: item.amt,
type: 'invoice',
description: item.description,
timestamp: item.timestamp,
}
})
}
}
public getMovements: (
setTransactions: (transactions: WalletAction[]) => void,
setInvoices: (transactions: WalletAction[]) => void,
setUpdatedAt: (updatedAt: string) => void,
) => Promise<void> = async (setTransactions, setInvoices, setUpdatedAt) => {
if (!this.config) return
setTransactions((await this.getTransactions()) ?? [])
setInvoices((await this.getInvoices()) ?? [])
setUpdatedAt(`${getUnixTime(new Date())}-movements`)
}
public refresh: (params: any) => Promise<{ config: LndHubConfig; status: number } | undefined> =
async (params) => {
let requestParams:
| { type: string; refresh_token?: string; login?: string; password?: string }
| undefined
if (this.config?.refreshToken) {
requestParams = {
type: 'refresh_token',
refresh_token: this.config?.refreshToken,
}
params.uri = this.config?.url
} else if (params.login !== '' && params.password !== '' && params.uri !== '') {
requestParams = {
type: 'auth',
login: params.login,
password: params.password,
}
}
if (params?.uri) {
const response = await axios.post(`${params.uri}/auth`, {}, { params: requestParams })
if (response) {
return {
config: {
accessToken: response.data.access_token,
refreshToken: response.data.refresh_token,
url: params.uri,
},
status: response.status,
}
}
}
}
}
export default LndHub

View File

@ -0,0 +1,9 @@
export interface WalletAction {
id: string
monto: number
type: 'invoice' | 'transaction'
description: string
timestamp: number
}
export default WalletAction

View File

@ -80,7 +80,7 @@
"@types/react-native-vector-icons": "^6.4.13",
"@types/react-test-renderer": "^18.0.0",
"@types/uuid": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^5.43.0",
"@typescript-eslint/eslint-plugin": "^5.56.0",
"babel-jest": "^29.4.3",
"eslint": "^8.36.0",
"eslint-config-prettier": "^8.6.0",
@ -94,10 +94,10 @@
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.4.3",
"metro-react-native-babel-preset": "^0.75.0",
"metro-react-native-babel-preset": "^0.76.0",
"prettier": "^2.8.4",
"react-test-renderer": "18.2.0",
"typescript": "^4.9.4"
"typescript": "^5.0.2"
},
"resolutions": {
"@types/react": "^17"

108
yarn.lock
View File

@ -1798,7 +1798,7 @@
dependencies:
"@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@^5.30.5", "@typescript-eslint/eslint-plugin@^5.43.0":
"@typescript-eslint/eslint-plugin@^5.30.5":
version "5.55.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.55.0.tgz#bc2400c3a23305e8c9a9c04aa40933868aaaeb47"
integrity sha512-IZGc50rtbjk+xp5YQoJvmMPmJEYoC53SiKPXyqWfv15XoD2Y5Kju6zN0DwlmaGJp1Iw33JsWJcQ7nw0lGCGjVg==
@ -1814,6 +1814,22 @@
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/eslint-plugin@^5.56.0":
version "5.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.56.0.tgz#e4fbb4d6dd8dab3e733485c1a44a02189ae75364"
integrity sha512-ZNW37Ccl3oMZkzxrYDUX4o7cnuPgU+YrcaYXzsRtLB16I1FR5SHMqga3zGsaSliZADCWo2v8qHWqAYIj8nWCCg==
dependencies:
"@eslint-community/regexpp" "^4.4.0"
"@typescript-eslint/scope-manager" "5.56.0"
"@typescript-eslint/type-utils" "5.56.0"
"@typescript-eslint/utils" "5.56.0"
debug "^4.3.4"
grapheme-splitter "^1.0.4"
ignore "^5.2.0"
natural-compare-lite "^1.4.0"
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/parser@^5.30.5", "@typescript-eslint/parser@^5.43.0":
version "5.55.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.55.0.tgz#8c96a0b6529708ace1dcfa60f5e6aec0f5ed2262"
@ -1832,6 +1848,14 @@
"@typescript-eslint/types" "5.55.0"
"@typescript-eslint/visitor-keys" "5.55.0"
"@typescript-eslint/scope-manager@5.56.0":
version "5.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.56.0.tgz#62b4055088903b5254fa20403010e1c16d6ab725"
integrity sha512-jGYKyt+iBakD0SA5Ww8vFqGpoV2asSjwt60Gl6YcO8ksQ8s2HlUEyHBMSa38bdLopYqGf7EYQMUIGdT/Luw+sw==
dependencies:
"@typescript-eslint/types" "5.56.0"
"@typescript-eslint/visitor-keys" "5.56.0"
"@typescript-eslint/type-utils@5.55.0":
version "5.55.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.55.0.tgz#74bf0233523f874738677bb73cb58094210e01e9"
@ -1842,11 +1866,26 @@
debug "^4.3.4"
tsutils "^3.21.0"
"@typescript-eslint/type-utils@5.56.0":
version "5.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.56.0.tgz#e6f004a072f09c42e263dc50e98c70b41a509685"
integrity sha512-8WxgOgJjWRy6m4xg9KoSHPzBNZeQbGlQOH7l2QEhQID/+YseaFxg5J/DLwWSsi9Axj4e/cCiKx7PVzOq38tY4A==
dependencies:
"@typescript-eslint/typescript-estree" "5.56.0"
"@typescript-eslint/utils" "5.56.0"
debug "^4.3.4"
tsutils "^3.21.0"
"@typescript-eslint/types@5.55.0":
version "5.55.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.55.0.tgz#9830f8d3bcbecf59d12f821e5bc6960baaed41fd"
integrity sha512-M4iRh4AG1ChrOL6Y+mETEKGeDnT7Sparn6fhZ5LtVJF1909D5O4uqK+C5NPbLmpfZ0XIIxCdwzKiijpZUOvOug==
"@typescript-eslint/types@5.56.0":
version "5.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.56.0.tgz#b03f0bfd6fa2afff4e67c5795930aff398cbd834"
integrity sha512-JyAzbTJcIyhuUhogmiu+t79AkdnqgPUEsxMTMc/dCZczGMJQh1MK2wgrju++yMN6AWroVAy2jxyPcPr3SWCq5w==
"@typescript-eslint/typescript-estree@5.55.0":
version "5.55.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.55.0.tgz#8db7c8e47ecc03d49b05362b8db6f1345ee7b575"
@ -1860,6 +1899,19 @@
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/typescript-estree@5.56.0":
version "5.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.56.0.tgz#48342aa2344649a03321e74cab9ccecb9af086c3"
integrity sha512-41CH/GncsLXOJi0jb74SnC7jVPWeVJ0pxQj8bOjH1h2O26jXN3YHKDT1ejkVz5YeTEQPeLCCRY0U2r68tfNOcg==
dependencies:
"@typescript-eslint/types" "5.56.0"
"@typescript-eslint/visitor-keys" "5.56.0"
debug "^4.3.4"
globby "^11.1.0"
is-glob "^4.0.3"
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/utils@5.55.0", "@typescript-eslint/utils@^5.10.0":
version "5.55.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.55.0.tgz#34e97322e7ae5b901e7a870aabb01dad90023341"
@ -1874,6 +1926,20 @@
eslint-scope "^5.1.1"
semver "^7.3.7"
"@typescript-eslint/utils@5.56.0":
version "5.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.56.0.tgz#db64705409b9a15546053fb4deb2888b37df1f41"
integrity sha512-XhZDVdLnUJNtbzaJeDSCIYaM+Tgr59gZGbFuELgF7m0IY03PlciidS7UQNKLE0+WpUTn1GlycEr6Ivb/afjbhA==
dependencies:
"@eslint-community/eslint-utils" "^4.2.0"
"@types/json-schema" "^7.0.9"
"@types/semver" "^7.3.12"
"@typescript-eslint/scope-manager" "5.56.0"
"@typescript-eslint/types" "5.56.0"
"@typescript-eslint/typescript-estree" "5.56.0"
eslint-scope "^5.1.1"
semver "^7.3.7"
"@typescript-eslint/visitor-keys@5.55.0":
version "5.55.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.55.0.tgz#01ad414fca8367706d76cdb94adf788dc5b664a2"
@ -1882,6 +1948,14 @@
"@typescript-eslint/types" "5.55.0"
eslint-visitor-keys "^3.3.0"
"@typescript-eslint/visitor-keys@5.56.0":
version "5.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.56.0.tgz#f19eb297d972417eb13cb69b35b3213e13cc214f"
integrity sha512-1mFdED7u5bZpX6Xxf5N9U2c18sb+8EvU3tyOIj6LQZ5OOvnmj8BVeNNP603OFPm5KkS1a7IvCIcwrdHXaEMG/Q==
dependencies:
"@typescript-eslint/types" "5.56.0"
eslint-visitor-keys "^3.3.0"
abort-controller@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
@ -2144,6 +2218,13 @@ available-typed-arrays@^1.0.5:
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
axios@^0.21.1:
version "0.21.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
dependencies:
follow-redirects "^1.14.0"
axios@^1.2.6, axios@^1.3.3:
version "1.3.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.4.tgz#f5760cefd9cfb51fd2481acf88c05f67c4523024"
@ -3999,7 +4080,7 @@ flow-parser@^0.121.0:
resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.121.0.tgz#9f9898eaec91a9f7c323e9e992d81ab5c58e618f"
integrity sha512-1gIBiWJNR0tKUNv8gZuk7l9rVX06OuLzY9AoGio7y/JT4V1IZErEMEq2TJS+PFcw/y0RshZ1J/27VfK1UQzYVg==
follow-redirects@^1.15.0:
follow-redirects@^1.14.0, follow-redirects@^1.15.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
@ -5452,6 +5533,14 @@ lines-and-columns@^1.1.6:
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
lnbits@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/lnbits/-/lnbits-1.1.5.tgz#ffe4bec2c1abddba3ad5c1277fcad0956b2fe712"
integrity sha512-RPCBNsKKxlyQTHPKdU66iiXFBz6SuISVVkxJoSZY3Z+CBEzOu6xpgzZtQcZTbc1BCLqQc6HeK4qtfByKWjBTmg==
dependencies:
axios "^0.21.1"
typescript "^4.1.3"
lnurl-pay@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/lnurl-pay/-/lnurl-pay-2.2.0.tgz#aaf1d3f997d113ea54ca65f5d9de8020005c6e91"
@ -5751,10 +5840,10 @@ metro-react-native-babel-preset@0.72.3:
"@babel/template" "^7.0.0"
react-refresh "^0.4.0"
metro-react-native-babel-preset@^0.75.0:
version "0.75.1"
resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.75.1.tgz#370bb3bba3ca83b3be1d8b0ab628271c864491cd"
integrity sha512-a4Se/koIVsH+wmfWsSOiRpFLBSICJcbd6o1wv37QRoFSnH7mYXDOfYxNBZYX46PwN1QwmgR49Iwsef79JOaJMg==
metro-react-native-babel-preset@^0.76.0:
version "0.76.0"
resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.76.0.tgz#440a0e8965b2eb01afa391ef95575faeed67636b"
integrity sha512-2sM6dy9uAbuQlg7l/VOdiudUUMFRkABJ1YLkZU6Fpqi/rJCXn4fbF0pO+TwCFbBYNIQBY50clv9RPvD2n64hXg==
dependencies:
"@babel/core" "^7.20.0"
"@babel/plugin-proposal-async-generator-functions" "^7.0.0"
@ -7981,11 +8070,16 @@ typeforce@^1.11.3:
resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc"
integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==
typescript@^4.9.4:
typescript@^4.1.3:
version "4.9.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
typescript@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.2.tgz#891e1a90c5189d8506af64b9ef929fca99ba1ee5"
integrity sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==
uglify-es@^3.1.9:
version "3.3.9"
resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677"