Last active
March 23, 2023 07:56
-
-
Save michael34435/0923e632bfe668c3af7e90bd8c7b9bee to your computer and use it in GitHub Desktop.
Edited from ChatGPT GPT-4. Lightweight LN payment gateway solution based on Bitfinex.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Koa = require('koa'); | |
const Router = require('koa-router'); | |
const bodyParser = require('koa-bodyparser'); | |
const Queue = require('bull'); | |
const crypto = require('crypto'); | |
const app = new Koa(); | |
const router = new Router(); | |
const paymentQueue = new Queue('payment-processing', 'redis://127.0.0.1:6379'); | |
const API_KEY = 'YOUR_BITFINEX_SECRET_OR_KEY'; | |
const API_SECRET = 'YOUR_BITFINEX_SECRET_OR_KEY'; | |
router.post('/invoices', async (ctx) => { | |
const { currency, amount } = ctx.request.body; | |
const btcAmount = await convertToBTC(currency, amount); | |
const data = await createInvoice(btcAmount); | |
// Add a task to the queue to process payment | |
paymentQueue.add({ | |
paymentHash: data.payment_hash | |
}, { | |
attempts: Number.MAX_SAFE_INTEGER, | |
backoff: { | |
type: 'exponential', | |
delay: 100, | |
} | |
}); | |
ctx.body = { | |
data, | |
}; | |
}); | |
app.use(bodyParser()); | |
app.use(router.routes()); | |
app.use(router.allowedMethods()); | |
const port = 3000; | |
app.listen(port, () => console.log(`Server started on port ${port}`)); | |
paymentQueue.process(async (job, done) => { | |
try { | |
const paymentHash = job.data.paymentHash; | |
// Wait for invoice payment | |
const paymentData = await waitForPayment(new Date(), paymentHash); | |
// Process payment | |
await processPayment(paymentData); | |
done(); | |
} catch (error) { | |
done(error) | |
} | |
}); | |
const delay = ms => new Promise(r => setTimeout(r, ms)); | |
async function convertToBTC(currency, amount) { | |
const endpoint = `https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=${currency.toLowerCase()}`; | |
const response = await fetch(endpoint); | |
if (!response.ok) { | |
throw new Error('Error fetching conversion rate'); | |
} | |
const data = await response.json(); | |
const conversionRate = data.bitcoin[currency.toLowerCase()]; | |
return amount / conversionRate; | |
} | |
async function createInvoice(btcAmount) { | |
const endpoint = '/v2/auth/w/deposit/invoice'; | |
const body = { | |
amount: btcAmount.toFixed(8).toString(), | |
wallet: 'exchange', | |
currency: 'LNX', | |
}; | |
const response = await bitfinexAPI(endpoint, 'POST', body); | |
const data = await response.json(); | |
if (!response.ok) { | |
throw new Error(`Error creating invoice: ${data.message}`); | |
} | |
const [payment_hash, invoice] = data; | |
return { | |
invoice, | |
payment_hash, | |
}; | |
} | |
function waitForPayment(timer, paymentHash) { | |
return new Promise((resolve, reject) => { | |
const checkPaymentStatus = async () => { | |
const endpoint = `/v1/ui_movements`; | |
const getTxFromUI = async (page = 1) => { | |
const response = await bitfinexAPI(endpoint, 'POST', { page, type: 'deposits' }, 500); | |
if (!response.ok) { | |
return false; | |
} | |
const deposits = await response.json(); | |
for (const deposit of deposits) { | |
if (deposit.txid === paymentHash && deposit.status === 'Completed') { | |
// TODO: you can add webhook here | |
return deposit; | |
} | |
} | |
const elapsed = ((new Date() - timer) / 1000); | |
if (elapsed >= 3600) { | |
return {}; | |
} | |
if (deposits.length === 0) { | |
return getTxFromUI(); | |
} | |
if (((new Date() - (deposits[0].mts_started * 1000)) / 1000) >= 3600) { | |
return {}; | |
} | |
return getTxFromUI(page + 1); | |
}; | |
const tx = await getTxFromUI(); | |
if (!tx) { | |
reject(new Error('Unable to find tx from Bitfinex')); | |
return; | |
} | |
resolve(tx); | |
return; | |
}; | |
return checkPaymentStatus(); | |
}); | |
} | |
async function processPayment(paymentData) { | |
await exchangeToMainnetBTC(paymentData.amount); | |
const response = await bitfinexAPI('/v2/auth/r/wallets', 'POST'); | |
const wallets = await response.json(); | |
for (const wallet of wallets) { | |
if (wallet[0] === 'exchange' && wallet[1] === 'BTC') { | |
const mainnetBTC = wallet[2]; | |
if (mainnetBTC >= 0.00006) { | |
await sellBTCforUSDT(mainnetBTC); | |
} | |
} | |
} | |
} | |
async function exchangeToMainnetBTC(lnBTC) { | |
const endpoint = '/v2/auth/w/transfer'; | |
const body = { | |
from: 'exchange', | |
to: 'exchange', | |
currency: 'LNX', | |
currency_to: 'BTC', | |
amount: lnBTC.toString(), | |
}; | |
const response = await bitfinexAPI(endpoint, 'POST', body); | |
const data = await response.json(); | |
if (!response.ok) { | |
throw new Error(`Error exchanging LN BTC to mainnet BTC: ${data.message}`); | |
} | |
} | |
async function sellBTCforUSDT(btcAmount) { | |
const endpoint = '/v2/auth/w/order/submit'; | |
const body = { | |
type: 'EXCHANGE MARKET', | |
symbol: 'tBTCUST', | |
amount: (-btcAmount).toString(), | |
}; | |
const response = await bitfinexAPI(endpoint, 'POST', body); | |
const data = await response.json(); | |
if (!response.ok) { | |
throw new Error(`Error selling BTC for USDT: ${data.message}`); | |
} | |
} | |
async function bitfinexAPI(endpoint, method, body = null, timer = 0) { | |
const nonce = (Date.now() * (/\/v1\//.test(endpoint) ? 1 : 1000)).toString(); | |
const requestBody = body ? JSON.stringify({ ...body, request: endpoint, nonce }) : ''; | |
const path = endpoint; | |
const signature = `/api${endpoint}${nonce}${requestBody}`; | |
const payload = Buffer.from(requestBody) | |
.toString('base64') | |
const sig = crypto | |
.createHmac('sha384', API_SECRET) | |
.update(signature) | |
.digest('hex'); | |
const oldSignature = crypto | |
.createHmac('sha384', API_SECRET) | |
.update(payload) | |
.digest('hex') | |
const headers = { | |
'Content-Type': 'application/json', | |
'bfx-nonce': nonce, | |
'bfx-apikey': API_KEY, | |
'bfx-signature': sig, | |
'x-bfx-payload': payload, | |
'x-bfx-apikey': API_KEY, | |
'x-bfx-signature': oldSignature, | |
}; | |
const params = { | |
method, | |
headers, | |
}; | |
if (method !== 'GET') { | |
params.body = requestBody; | |
} | |
const response = await fetch(`https://api.bitfinex.com${path}`, params); | |
return delay(timer).then(() => response); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment