Skip to content

Instantly share code, notes, and snippets.

@michael34435
Last active March 23, 2023 07:56
Show Gist options
  • Save michael34435/0923e632bfe668c3af7e90bd8c7b9bee to your computer and use it in GitHub Desktop.
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.
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