From 31dc719d70429e9791d10a077dbd2e7f43ccb886 Mon Sep 17 00:00:00 2001 From: Torkel Rogstad Date: Thu, 15 May 2025 08:46:01 +0200 Subject: [PATCH 1/2] index: parse withdrawal amount as float, more validation The withdrawal amount is passed into `sendtoaddress`. This number is in whole BTC, i.e. 0.01 BTC should be valid. --- index.js | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index a6d35ba..3660d10 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,5 @@ -const { exec } = require('child_process'); const express = require('express'); const crypto = require('crypto'); -const axios = require('axios'); const { config, verifyConfig } = require('./config'); const { makeRpcCallBTC } = require('./utils/bitcoin-rpc'); const { execThunderCli } = require('./utils/thunder-cli'); @@ -143,10 +141,20 @@ app.get('/balance', async (req, res) => { app.post('/withdraw', async (req, res) => { const { withdrawal_destination, withdrawal_amount, layer_2_chain_name } = req.body; + const missing_fields = []; // Validate required fields - if (!withdrawal_destination || !withdrawal_amount || !layer_2_chain_name) { + if (!withdrawal_destination) { + missing_fields.push('withdrawal_destination'); + } + if (!withdrawal_amount) { + missing_fields.push('withdrawal_amount'); + } + if (!layer_2_chain_name) { + missing_fields.push('layer_2_chain_name'); + } + if (missing_fields.length > 0) { return res.status(400).json({ - error: 'withdrawal_destination, withdrawal_amount, and layer_2_chain_name are required' + error: 'Missing required fields: ' + missing_fields.join(', ') }); } @@ -159,15 +167,29 @@ app.post('/withdraw', async (req, res) => { } // Validate withdrawal_amount is an integer - const amount = parseInt(withdrawal_amount); - if (isNaN(amount) || amount.toString() !== withdrawal_amount) { + const amount = Number.parseFloat(withdrawal_amount); + if (Number.isNaN(amount)) { + return res.status(400).json({ + error: 'withdrawal_amount must be a number' + }); + } + + if (amount <= 0) { + return res.status(400).json({ + error: 'withdrawal_amount must be positive' + }); + } + + // max withdrawal amount is 10% of our mainchain balance + const balance = await getBalanceBTC(); + const maxWithdrawalAmount = balance.info * 0.1; + if (amount > maxWithdrawalAmount) { return res.status(400).json({ - error: 'withdrawal_amount must be an integer' + error: `withdrawal amount is above max: ${maxWithdrawalAmount.toString()}` }); } // TODO validate address format - // TODO validate amount format // Check L2 chain name if (layer_2_chain_name != "Thunder" && layer_2_chain_name != "BitNames") { @@ -216,6 +238,7 @@ app.post('/withdraw', async (req, res) => { withdrawalRequest.hash = requestHash; // Track new withdrawal request + console.log("pushing new withdrawal request: ", withdrawalRequest); withdrawalRequests.push(withdrawalRequest); res.json({ From 375fde1c7e5c95934b3f23c3783fa2df2fbbfcea Mon Sep 17 00:00:00 2001 From: Torkel Rogstad Date: Thu, 15 May 2025 09:11:37 +0200 Subject: [PATCH 2/2] index: validate withdrawal address --- index.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 3660d10..6b9290e 100644 --- a/index.js +++ b/index.js @@ -53,7 +53,15 @@ async function sendToAddressBTC(address, amount) { } - +async function validateAddressBTC(address) { + try { + const info = await makeRpcCallBTC(config, 'validateaddress', [address]); + return { info }; + } catch (error) { + console.error('Failed to validate BTC address:', error); + throw error; + } +} // Thunder interaction @@ -189,7 +197,13 @@ app.post('/withdraw', async (req, res) => { }); } - // TODO validate address format + // validate withdrawal destination address + const addressValidation = await validateAddressBTC(withdrawal_destination); + if (!addressValidation.info.isvalid) { + const error = addressValidation.info.error ? `Invalid L1 BTC address: ${addressValidation.info.error}` : "Invalid L1 BTC address"; + return res.status(400).json({ error }); + } + // Check L2 chain name if (layer_2_chain_name != "Thunder" && layer_2_chain_name != "BitNames") {