0

I am creating a dummy crypto exchange for the practice where users sign up, sign in, trade assets and etc just like a regular exchange.

When the user deposits the funds using metamask I store all of its transaction details and check them against etherscan and bscscan API endpoints for txhash, from, to, and amount, if all match I store these details in a database with transactionID as a unique field.

Now the problem is that when the DB is empty and the user makes a tx using postman or curl transaction succeeds.

So how can I make a secure tx from the frontend so that the user won't be able to make a successful tx from postman or curl

Front-End

var email = (document.getElementById("email").textContent).trim();
var address = (document.getElementById("address").textContent).trim();

const web3 = new Web3(window.ethereum);

var addr = web3.utils.toChecksumAddress(document.getElementById("addr").textContent);

var erc20USDT = web3.utils.toChecksumAddress("0x3939B6ECc7F870bFA7177c6f5a90ede3dAB1666D");
var bep20USDT = web3.utils.toChecksumAddress("0x96B3ada252F60490Be97E363cDB47407f66b1Ad3");

var ownerAddress = web3.utils.toChecksumAddress(address);



var getABI = async(chain) =>
{
    
    var response = await fetch('/abi?asset='+chain,
    {
        method: 'GET',
        dataType: 'json',
    });

    return (JSON.stringify(await response.json()));
}

async function account() 
{
    var acc = await web3.eth.requestAccounts();
    return web3.utils.toChecksumAddress(acc[0])
}

async function tx(chain,contract_address,decimals)
{
    var amount = BigInt(web3.utils.toBN(document.getElementById('amount').value)*Math.pow(10,decimals)).toString();

    var SIGNING_DATA = (
        'email:'+email+'\ndata: 0x+'+((Math.floor(Math.random() * 200000000) + 1)*10**20).toString(16)+'\namount:'+document.getElementById('amount').value+'\naddress:'+await account()+'\nchain:'+chain
    )
    
    var FINAL_SIGNATURE = "For security purposes, firsly you will have to sign the transaction, then it will proceed\n\n"+web3.utils.toHex(SIGNING_DATA)
    
    var sig = await web3.eth.personal.sign(FINAL_SIGNATURE, await account())

    var encoded = window.btoa(encodeURIComponent(FINAL_SIGNATURE));

    var sec = await fetch('/tx/sec?msg='+encoded,
        {
            method: 'GET',
            dataType: 'json',
            headers:
            {
                "Tx":sig 
            }
        });

    var secRes = (JSON.stringify(await sec.json()));

    var contract = new web3.eth.Contract(JSON.parse(await getABI(chain)), contract_address);

    await contract.methods.transfer(ownerAddress, amount).send({from: await account()})
    .then(async (txhash) =>
    {
        if(secRes != "" || secRes != null)
        {
            let body = 
            {
                'email':email,
                //'amount': parseFloat(document.getElementById('amount').value),
                'csrf': document.getElementById('csrf').value,
                'txHash':await txhash.transactionHash,
                'from': await txhash.from,
                'chain': chain,
            };

            await fetch('/deposit',
            {
                method: 'POST',
                headers:
                {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json',
                    'Wtx' : secRes
                },
                body: JSON.stringify(body),
            })
            .then(async(response)=>
            {
                var message = JSON.stringify(await response.json());
                var msg = JSON.parse(message)

                if(response.status == 200)
                {
                    console.log(txhash);
                    $("#res").html('<div class="alert btn-success"><strong>Success!</strong>&nbsp;'+msg.msg+'</div>')
                    $("#res").show().delay(5000).fadeOut();
                }

                else
                {
                    $("#res").html('<div class="alert btn-danger"><strong>Error!</strong>&nbsp;'+msg.msg+'</div>')
                    $("#res").show().delay(5000).fadeOut();
                    //window.location.assign('/deposit')
                }
            })
        }

        else
        {
            $("#res").html('<div class="alert btn-danger"><strong>Error!</strong>&nbsp;Something went wrong!</div>')
            $("#res").show().delay(5000).fadeOut();
        }
    })
    .catch(err => 
        {
            $("#res").html('<div class="alert btn-danger"><strong>Error!</strong>&nbsp;'+err.message+'</div>')
            $("#res").show().delay(5000).fadeOut();
        })
}

const sign = async () => 
{
    let res = await fetch(`/sign?address=${await account()}`)
    let resBody = await res.json()

    if(res.status == 200)
    {
        let signature = await web3.eth.personal.sign(resBody.message, await account())

        let opts = 
        {
            method: 'POST',
            headers: 
            {
                'Accept': 'application/json',
                'Content-Type': 'application/json',
                'Web3': `${resBody.tempToken}`
            }
        }

        let res2 = await fetch(`/sign?signature=${signature}`, opts)
        let resBody2 = await res2.json()
        
        if(res2.ok)
        {
            $("#res").html('<div class="alert btn-success"><strong>Success!</strong>&nbsp;'+resBody2.msg+'</div>')
            $("#res").show().delay(5000).fadeOut();

            document.getElementById("connect").style.display = 'block';
            document.getElementById("authenticate").style.display = 'none';

            //window.location.assign("/deposit")
        }

        else
        {
            $("#res").html('<div class="alert btn-danger"><strong>Error!</strong>&nbsp;'+resBody2.msg+'</div>')
            $("#res").show().delay(5000).fadeOut();
        }
    }

    else
    {
        $("#res").html('<div class="alert btn-danger"><strong>Error!</strong>&nbsp;'+resBody.msg+'</div>')
        $("#res").show().delay(5000).fadeOut();
    }
}

window.addEventListener('load', async() => 
{
    if (typeof window.ethereum == 'undefined') 
    {
        document.getElementById("install").style.display = 'block';
        console.warn("You need to install MetaMask");
        document.getElementById("connect").style.display = 'none';
    }
     
    else
    {
        document.querySelector("#authenticate").addEventListener("click", sign);

        document.querySelector("#connect").addEventListener("click", async(event) =>
        {
            await ethereum.enable();
            
            if(addr != await account())
            {
                $("#res").html('<div class="alert btn-danger"><strong>Error!</strong>&nbsp;Invalid account please connect to Metamask with '+addr+' </div>')
                $("#res").show().delay(5000).fadeOut();
                document.getElementById("con").style.display = "block";
                document.getElementById("discon").style.display = "none";  
            }

            else
            {
                document.getElementById("con").style.display = "none";
                document.getElementById("discon").style.display = "block";
            }
        });

        document.querySelector("#deposit").addEventListener("click", async(event) =>
        {
            const chainID = await web3.eth.getChainId();
            //ETH
            if(chainID == 42/* 1337 */)
            {
                await tx("ETH",erc20USDT,6)
            }
            
            //BSC
            else if(chainID == 97 /* 122 */)
            {
                await tx("BSC",bep20USDT,18)
            }
            
            //MATIC
            /* else if(chainID == 100)
            {
                alert(await getABI('MATIC'));
            } */

            else
            {
                $("#res").html('<div class="alert btn-danger"><strong>Error!</strong>&nbsp;Only Ethereum & BSC are allowed! </div>')
                $("#res").show().delay(5000).fadeOut();
            }
        });

        document.querySelector("#disconnect").addEventListener("click", async(event) =>
        {
            document.getElementById("con").style.display = "block";
            document.getElementById("discon").style.display = "none";
            console.clear()
        });


    }
});

Back-End

var express = require('express');
var app = express();
var router = express.Router();
const atob = require("atob");
var dotenv = require("dotenv");
var cookieParser = require('cookie-parser');
var ethUtil = require('ethereumjs-util');
var randToken = require('rand-token');
var auth = require('../middleware/auth')
var authMenu = require("../middleware/auth-menu")
var KYCCheckerLevel1 = require("../middleware/KYCheckerLevel1");
var userData = require('../middleware/auth-data');
var getTxData = require("../middleware/getTxDATA_API");
const Models = require('../models');
const BigNumber = require('bignumber.js');
const User = Models.User;
var jwt = require('jsonwebtoken')
const Transactions = Models.transactionIds
var ejs = require('ejs');
var path = require('path');
var send = require('../middleware/mail')

var f = 'Tech Team';
var subject = 'New Deposit';

dotenv.config();

app.use(cookieParser());

router.get('/', auth, KYCCheckerLevel1, authMenu, async function(req, res, next) 
{   
    token = randToken.uid(200)
    res.cookie('token', token,{secure:false, sameSite:'lax',httpOnly: true});
    authMenu(req,res,next,"deposit","Deposit Funds","","","","","","","","",token);

});

router.post('/', auth, KYCCheckerLevel1, authMenu, async function(req, res, next) 
{   
    try
    {
        var authData = await userData(req)
    
        var wtx = req.headers['wtx'];
    
        var token = req.cookies.token;
        var email = req.body.email;
        //var amount = req.body.amount;
        var csrf = req.body.csrf;
        var txHash = req.body.txHash;
        var from = req.body.from;
        var chain = req.body.chain;
    
        async function updateData(req,res, txID, chain,amount)
        {
            var txData = 
                    {
                        f_key: authData.id,
                        transactionID: txID,
                        chain: chain,
                        amount: amount
                    }
    
                    await Transactions.create(txData)
                .then(async()=>
                {
                    await User.update(
                        {
                            USDT_balance : Models.sequelize.literal('USDT_balance+'+amount),
                        },
                        {
                            where:
                            {
                                email : authData.email,
                            }
                        })
                    .then(async()=>
                    {
                        res.status(200).json({msg:`Your transaction: ${txID} for $${amount} is successful!`})
                        
                        ejs.renderFile(path.join(__dirname, '../views/email_templates/deposit.ejs'), 
                        {
                            name: authData.full_name,
                            amount: amount,
                        })
                        .then(async(template) =>
                        {
                            send(f,authData.email,subject,template)
                        })
    
                        return true;
                    })
                    .catch(async() =>
                    {
                        res.status(401).json({msg:"Failed to update user data!"})
                        return false;
                    })
                })
                .catch(async(e) =>
                {
                    res.status(401).json({msg:"Transaction got failed!"})
                    return false
                })
                
        }
    
        async function userDeposit(url,api,chain,hash,dec)
        {
            var tx =  await getTxData(url,api,hash);
    
            if(typeof tx != "undefined" && tx != "" && tx != null)
            {
                var txAmount = parseInt(tx.logs["0"].data,16)/dec
    
                var owner_Address = ethUtil.toChecksumAddress("0x"+(tx.logs["0"].topics["2"]).slice(26))
                
                if(
                    from == tx.from && 
                    authData.address == tx.from && 
                    txHash == tx.logs["0"].transactionHash &&
                    owner_Address == process.env.OWNER)
                {
                    await updateData(req,res,tx.logs["0"].transactionHash,chain,txAmount)
                } 
    
                else
                {
                    res.status(401).json({msg:"Transaction was unsuccessful!"})    
                }
            }
    
            else
            {
                res.status(403).json({msg:"Someome tried to forge the transaction, exiting..."})
            }
        }

        var verifiedJWT = await jwt.verify(wtx, process.env.TX_SIGNING_KEY);
            
        
    
        if(verifiedJWT.address == from)
        {
            res.status(403).redirect('/slsl');
        }
    
        else if(
            token != "" && 
            email != "" && 
            //amount != "" && 
            csrf != "" &&
            txHash != "" &&
            from != "" &&
            chain != "" &&
            token != null && 
            email != null && 
            //amount != null && 
            csrf != null && 
            txHash != null && 
            from != null &&
            chain != null)
        {
            if(token == csrf)
            {
                if(email == "")
                {
                     res.status(401).json({msg:"Email can't be empty!"})
                }
            
                else
                {
                    if(authData.email == email)
                    {
                        if(chain == "ETH")
                        {
                            await userDeposit("https://api-kovan.etherscan.io/api",process.env.ETHERSCAN_API_KEY,"ETH",txHash,10000000)
                            
                        }
    
                        else if(chain == "BSC")
                        {
                            var dec = new BigNumber("1000000000000000000");
                            await userDeposit("https://api-testnet.bscscan.com/api",process.env.BSCSCAN_API_KEY,"BSC",txHash,dec)
                        }
    
                        else
                        {
                            res.status(401).json({msg:"Only Ethereum & Binance Smart Chain are allowed!"})
                        }
                    }
            
                    else
                    {
                        res.status(401).json({msg:"User doesn't exist!"})
                    }
                }
            }
        
            else
            {
                res.status(403).json({msg:"Request was tampered!"})
            }
        }
    
        else
        {
            res.status(401).json({msg:"All fields are required!"})
        }
    }

    catch(e)
    {
        res.status(403).json({msg:e.message})
    } 
});

module.exports =  router;


0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.