Skip to content

Commit

Permalink
Add more options for invoice creation
Browse files Browse the repository at this point in the history
  • Loading branch information
Dolu89 committed Oct 27, 2021
1 parent 03a156c commit b53eb91
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 24 deletions.
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ Una is a Lightning network node wrapper for LND, c-lightning, Eclair, LndHub, LN
## How to use it
``` typescript
// ES Module
import { Una, EBackendType } from 'una-wrapper'
import { Una, EBackendType, ICreateInvoice } from 'una-wrapper'
// Common JS
const { Una, EBackendType } = require('una-wrapper')
const { Una, EBackendType, ICreateInvoice } = require('una-wrapper')

// LND Rest
const hexMacaroon = '0201036...311c811'
Expand All @@ -32,8 +32,21 @@ const unaClient = new Una(EBackendType.LndRest, { url: 'https://127.0.0.1:8080',
// Eclair Rest
const unaClient = new Una(EBackendType.EclairRest, { url: 'http://127.0.0.1:8080', user: '', password: 'eclairpw' })

// Create an invoice of 15k satoshis with 'Hello' as memo
const newInvoice = await unaWrapper.createInvoice(15000, 'Hello')
/*
Create an invoice of 15k satoshis with 'Hello' as memo
Possible options are the following (from src/interface/i-create-invoice.ts)
{
amount: number
amountMsats: number
description: string
descriptionHash: string
expireIn?: number
fallbackAddress?: string
paymentPreimage?: string
}
*/
const invoiceCreate: ICreateInvoice = { amount: 15000, description: 'Hello' }
const newInvoice = await unaWrapper.createInvoice(invoiceCreate)
// Get invoice created previously
const invoice = await unaWrapper.getInvoice(newInvoice.paymentHash)

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "una-wrapper",
"version": "0.0.3-beta",
"version": "0.0.5-beta",
"description": "Universal Node API",
"scripts": {
"build": "tsup src/index.ts --format esm,cjs",
Expand Down
30 changes: 26 additions & 4 deletions src/backends/eclair-rest/eclair-rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { FormData } from 'formdata-node'
import { FormDataEncoder } from 'form-data-encoder'
import { Readable } from 'stream'
import { IBackend } from '..'
import { IEclairRest, Invoice } from '../../interfaces'
import { ICreateInvoice, IEclairRest, Invoice } from '../../interfaces'
import { EHttpVerb } from '../../enums'
import { IInvoiceCreated, IInvoiceLookup } from '.'

Expand All @@ -14,14 +14,33 @@ export default class EclairRest implements IBackend {
this.eclairRest = eclairRest
}

public async createInvoice (amount: number, memo: string): Promise<Invoice> {
public async createInvoice (invoice: ICreateInvoice): Promise<Invoice> {
const amountMsat = invoice.amountMsats !== undefined ? invoice.amountMsats : invoice.amount * 1000

const form = new FormData()
form.append('description', memo)
form.append('amountMsat', String(amount * 1000))
form.append('amountMsat', String(amountMsat))

if (invoice.description !== undefined && invoice.descriptionHash === undefined) {
form.append('description', invoice.description)
} else if (invoice.descriptionHash !== undefined && invoice.description === undefined) {
form.append('descriptionHash', invoice.descriptionHash)
} else {
throw new Error('You must specify either description or descriptionHash, but not both')
}
if (invoice.expireIn !== undefined) {
form.append('expireIn', invoice.expireIn)
}
if (invoice.fallbackAddress !== undefined) {
form.append('fallbackAddress', invoice.fallbackAddress)
}
if (invoice.paymentPreimage !== undefined) {
form.append('paymentPreimage', invoice.paymentPreimage)
}

const options = this.getRequestOptions(EHttpVerb.POST, form)
const response = await fetch(`${this.eclairRest.url}/createinvoice`, options)
const responseData = await response.json() as IInvoiceCreated

return await this.getInvoice(responseData.paymentHash)
}

Expand All @@ -31,6 +50,7 @@ export default class EclairRest implements IBackend {
const options = this.getRequestOptions(EHttpVerb.POST, data)
const response = await fetch(this.eclairRest.url + '/getreceivedinfo', options)
const responseData = await response.json() as IInvoiceLookup

return this.toInvoice(responseData)
}

Expand All @@ -40,6 +60,7 @@ export default class EclairRest implements IBackend {

private toInvoice (invoice: IInvoiceLookup): Invoice {
const settled = invoice.status.type === 'received'

return {
bolt11: invoice.paymentRequest.serialized,
amount: invoice.paymentRequest.amount / 1000,
Expand All @@ -56,6 +77,7 @@ export default class EclairRest implements IBackend {

private getRequestOptions (method: EHttpVerb, form: FormData): RequestInit {
const encoder = new FormDataEncoder(form)

return {
method: method,
headers: {
Expand Down
4 changes: 2 additions & 2 deletions src/backends/i-backend.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Invoice } from '../interfaces'
import { ICreateInvoice, Invoice } from '../interfaces'

export default interface IBackend {
createInvoice: (amount: number, memo: string) => Promise<Invoice>
createInvoice: (invoice: ICreateInvoice) => Promise<Invoice>
getInvoice: (hash: string) => Promise<Invoice>
}
18 changes: 14 additions & 4 deletions src/backends/lnd-rest/lnd-rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as https from 'https'
import fetch, { RequestInit } from 'node-fetch'
import { IBackend } from '..'
import { EHttpVerb } from '../../enums'
import { ILndRest, Invoice } from '../../interfaces'
import { ICreateInvoice, ILndRest, Invoice } from '../../interfaces'
import { base64ToHex } from '../tools'

export default class LndRest implements IBackend {
Expand All @@ -12,21 +12,30 @@ export default class LndRest implements IBackend {
this.lndRest = lndRest
}

public async createInvoice (amount: number, memo: string): Promise<Invoice> {
public async createInvoice (invoice: ICreateInvoice): Promise<Invoice> {
const amountMsat = invoice.amountMsats !== undefined ? invoice.amountMsats : invoice.amount * 1000

const body = {
value_msat: amount * 1000,
memo
value_msat: amountMsat,
expiry: invoice.expireIn,
fallback_addr: invoice.fallbackAddress,
paymentPreimage: invoice.paymentPreimage,
memo: invoice.description,
description_hash: invoice.descriptionHash
}

const options = this.getRequestOptions(EHttpVerb.POST, body)
const response = await fetch(this.lndRest.url + '/v1/invoices', options)
const responseData = await response.json() as ILndInvoice

return await this.getInvoice(base64ToHex(responseData.r_hash))
}

public async getInvoice (hash: string): Promise<Invoice> {
const options = this.getRequestOptions(EHttpVerb.GET)
const response = await fetch(this.lndRest.url + '/v1/invoice/' + hash, options)
const responseData = await response.json() as ILndInvoice

return this.toInvoice(responseData)
}

Expand All @@ -53,6 +62,7 @@ export default class LndRest implements IBackend {
const agent = new https.Agent({
rejectUnauthorized: false
})

return {
method: method,
agent,
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { Una } from './una.js'
export { IEclairRest, ILndRest, Invoice } from './interfaces'
export { IEclairRest, ILndRest, Invoice, ICreateInvoice } from './interfaces'
export { EBackendType } from './enums'
36 changes: 36 additions & 0 deletions src/interfaces/i-create-invoice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export default interface ICreateInvoice {
/**
* Amount in msats. You must specify either amount or amountMsats, but not both.
*/
amount: number

/**
* Amount in sats. You must specify either amount or amountMsats, but not both.
*/
amountMsats: number

/**
* Description. You must specify either description or descriptionHash, but not both.
*/
description: string

/**
* Description hash in 32 bytes hex string. You must specify either description or descriptionHash, but not both.
*/
descriptionHash: string

/**
* Number of seconds that the invoice will be valid
*/
expireIn?: number

/**
*An on-chain fallback address to receive the payment
*/
fallbackAddress?: string

/**
* A user defined input for the generation of the paymentHash
*/
paymentPreimage?: string
}
3 changes: 2 additions & 1 deletion src/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ICreateInvoice from './i-create-invoice.js'
import IEclairRest from './i-eclair-rest.js'
import Invoice from './i-invoice.js'
import ILndRest from './i-lnd-rest.js'

export { IEclairRest, Invoice, ILndRest }
export { IEclairRest, Invoice, ILndRest, ICreateInvoice }
20 changes: 13 additions & 7 deletions src/una.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { EclairRest, IBackend, LndRest } from './backends'
import { EBackendType } from './enums'
import { IEclairRest, ILndRest, Invoice } from './interfaces'
import { ICreateInvoice, IEclairRest, ILndRest, Invoice } from './interfaces'

export class Una {
private readonly client: IBackend | undefined
Expand All @@ -23,22 +23,27 @@ export class Una {

/**
* Create an invoice
* @param amount amount in satoshis
* @param memo memo
* @returns Invoice
* @param invoice {ICreateInvoice} object
* @returns {Invoice} Invoice
*/
public async createInvoice (amount: number, memo: string): Promise<Invoice> {
public async createInvoice (invoice: ICreateInvoice): Promise<Invoice> {
if (this.client === undefined) {
throw new Error('No backend defined')
}
if (invoice.amount === undefined && invoice.amountMsats === undefined) {
throw new Error('amount or amountMsat must be defined')
}
if (invoice.description !== undefined && invoice.descriptionHash !== undefined) {
throw new Error('You must specify either description or descriptionHash, but not both')
}

return await this.client.createInvoice(amount, memo)
return await this.client.createInvoice(invoice)
}

/**
* Get an invoice previously created
* @param hash hex encoded payment hash
* @returns Invoice
* @returns {Invoice} Invoice
*/
public async getInvoice (hash: string): Promise<Invoice> {
if (this.client === undefined) {
Expand All @@ -57,6 +62,7 @@ export class Una {
const info = connectionInformation as IEclairRest
return info.url !== undefined && info.user !== undefined && info.password !== undefined
}

return false
}
}
Expand Down

0 comments on commit b53eb91

Please sign in to comment.