Files
Exchange/server/routes/wallet.ts
2025-11-07 22:24:40 +01:00

605 lines
35 KiB
TypeScript
Executable File

import * as express from 'express';
import retry from 'async-retry';
import * as _ from 'lodash'
import * as mongoose from 'mongoose'
import * as uuidv4 from 'uuid4'
import { User } from '../db/user'
import { Admin } from '../db/admin'
import { GetPrice } from '../db/getPrice';
import { Accepted_Offers } from '../db/acceptedOffers'
import { Active_Offers } from '../db/activeOffers'
import { userValidationRules, validate } from '../middlewares/validation'
import tryCatch from '../middlewares/tryCatch'
import { rateLimiterMiddleware } from '../middlewares/preventBruteForce'
import { isAuthorized } from '../middlewares/auth'
import successRes from '../middlewares/response'
import * as redis from '../api/redis'
import myError from '../api/myError'
import { logger } from '../api/logger'
import { suggestOffers } from '../api/suggestOffers'
import * as etheriumWallet from '../api/walletApi/etheriuem'
import { transferFromExchangeApi } from '../api/walletApi/transferFromExchange'
import { transferToExchangeApi } from '../api/walletApi/transferToExchange'
import { transferToExchangeByIdApi } from '../api/walletApi/transferToExchangeById'
export const walletRoutes = express.Router()
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
walletRoutes.get('/getEtheriumNonce',
isAuthorized,
userValidationRules('query', 'etheriumAccountAddress'),
validate,
tryCatch((req, res, next) => {
const etheriumAccountAddress = req.query.etheriumAccountAddress
etheriumWallet.getEtheriumNonce(etheriumAccountAddress)
.then((result)=>{
successRes(res, 'Getting nonce completed successfully', Number(result), {})
})
.catch((err)=>{
next(err)
})
}))
walletRoutes.post('/buyCurrency',
isAuthorized,
userValidationRules('body', 'currency'),
validate,
tryCatch(async (req, res, next) => {
const userId = req.session.userId
const currency = req.body.currency
const quantity = req.body.quantity
let price
GetPrice.findOne({ $and: [{ currency: currency }, { userId: userId } ] })
.then(async (priceObj) => {
if(priceObj && priceObj.currency.toString() === currency.toString()&& priceObj.userId.toString() === userId.toString()) {
const curRialPrice = await redis.getCurrentPrice(currency)
if (curRialPrice > priceObj.rialPricePerUnit) {
price = priceObj.rialPricePerUnit
} else {
price = curRialPrice
}
redis.hashGetAll("RIAL")
.then(async(rialObject: any) => {
if(Boolean(process.env.BUYFROMOFFERS)) {
let errorCounter = 5;
retry(async bail => {
const session = await mongoose.startSession()
let offerIds = []
const suggestedOffers = await suggestOffers({ userId, price: priceObj.rialPricePerUnit, capacity: quantity, offerType: 'buy', currencyId: currency, rialId: rialObject.id })
if(suggestedOffers && Array.isArray(suggestedOffers.subset)) {
offerIds = await suggestedOffers.subset.map((e) => {
return e.id
})
} else {
throw 'No suggested offers!'
}
return session.withTransaction(async() => {
return Active_Offers.find({ offerId: { $in: offerIds } }).session(session)
.then((offers) => {
if(offers.length !== offerIds.length) {
const error = new myError (
);
throw error
} else {
return User.findOne({ _id : userId }).session(session)
.then(async (buyerUser: any) => {
return Admin.findOne({}).session(session)
.then(async (admin: any) => {
let givenObjInBuyer = _.find(buyerUser.wallet, (e) => e.currency.toString() === priceObj.currency.toString())
let takenObjInBuyer = _.find(buyerUser.wallet, (e) => e.currency.toString() === rialObject.id.toString())
if(!givenObjInBuyer) {
const givenCurrencyValueObject = {
currency : priceObj.currency,
value : 0
}
buyerUser.wallet.push(givenCurrencyValueObject)
givenObjInBuyer = givenCurrencyValueObject
}
const orderAcceptor = offers.map((individualOffer) => {
const buyOrderId = uuidv4()
const sellerId = individualOffer.userId
return User.findOne({ _id : sellerId }).session(session)
.then(async (creator: any) => {
if(creator && creator._id.toString() === sellerId.toString()) {
let givenObjInCreWal = _.find(creator.wallet, (e) => e.currency.toString() === individualOffer.curGivenId.toString())
if(givenObjInCreWal) {
givenObjInCreWal.commitment -= Number(individualOffer.curGivenVal)
let takenObjInCreWal = _.find(creator.wallet, (i) => i.currency.toString() === individualOffer.curTakenId.toString())
if(takenObjInCreWal) {
takenObjInCreWal.value += individualOffer.curTakenVal
} else {
const currencyObj = {
currency : individualOffer.curTakenId,
value : individualOffer.curTakenVal
}
creator.wallet.push(currencyObj)
takenObjInCreWal = currencyObj
}
takenObjInBuyer.value -= individualOffer.curTakenVal
givenObjInBuyer.value += individualOffer.curGivenVal
const bodyAccOffer = {
acceptor : admin._id,
creator: sellerId,
offerId: individualOffer.offerId,
curGivenId: individualOffer.curGivenId,
curGivenVal: individualOffer.curGivenVal,
curTakenId: individualOffer.curTakenId,
curTakenVal: individualOffer.curTakenVal,
offeredDate: individualOffer.created_at,
expiredDate : individualOffer.expDate,
buyOrderId
}
await creator.save()
await Accepted_Offers.create([bodyAccOffer], { session })
} else {
const error = new myError(
'creator wallet does not have given object',
400,
5,
'در کیف پول فرشنده ارز مورد نظر پیدا نشد',
'خطا رخ داد'
)
throw(error)
}
} else {
const error = new myError(
'failed to accept an offer',
400,
5,
'موفق به پذیرش یک آفر نشدیم',
'خطا رخ داد'
)
throw(error)
}
})
.catch((err) => {
throw(err)
})
})
return Promise.all(orderAcceptor)
.then(async() => {
// buyerUser.password = undefined
await buyerUser.save()
})
.catch((err)=>{
throw(err)
})
}).catch((err)=>{
throw(err)
})
})
.catch((err) => {
throw(err)
})
}
})
.catch((err) => {
throw (err)
})
})
.then(() => {
successRes(res, 'Buying process finished successfully', true, {})
})
.catch((err) => {
errorCounter--
if(errorCounter==0) {
next(err)
} else {
throw(err)
}
})
.finally(() => {
session.endSession()
})
}, {
maxTimeout: 5000,
retries: 5
}
)
} else {
const session = await mongoose.startSession()
session.withTransaction(async() => {
return User.findOne({ _id : userId }).session(session)
.then(async (user: any) => {
let rialWalInUser = _.find(user.wallet, (i) => { return i.currency.toString() === rialObject.id.toString() })
if(rialWalInUser.value >= priceObj.rialPrice) {
return Admin.findOne({}).session(session)
.then(async (admin: any) => {
let rialWalInAdmin = _.find(admin.wallet, (i) => { return i.currency.toString() === rialObject.id.toString() })
let curWalInUser = _.find(user.wallet, (i) => { return i.currency.toString() === currency.toString() })
let curWalInAdmin = _.find(admin.wallet, (i) => { return i.currency.toString() === currency.toString() })
if(curWalInUser ) {
curWalInUser.value += Number(priceObj.quantity)
curWalInAdmin.value -= Number(priceObj.quantity)
rialWalInUser.value -= Number(priceObj.rialPrice)
rialWalInAdmin.value += Number(priceObj.rialPrice)
await admin.save()
await user.save()
await priceObj.remove()
} else {
const bodyCurrency = {
currency : currency,
value : priceObj.quantity,
commitment : 0
}
user.wallet.push(bodyCurrency)
rialWalInUser.value -= priceObj.rialPrice
rialWalInAdmin.value += priceObj.rialPrice
curWalInAdmin.value -=priceObj.quantity
await admin.save()
await user.save()
await priceObj.remove()
}
})
.catch((err) => {
throw err
})
} else {
const error = new myError(
'User does not have enough rial credit in his/her wallet.',
400,
5,
'کاربر ارز ریالی کافی برای خرید را ندارد.',
'خطا رخ داد'
)
throw(error)
}
})
.catch((err) => {
throw(err)
})
})
.then(() => {
successRes(res, 'Buying process finished successfully', true, {})
})
.catch((err) => {
next(err)
})
.finally(() => {
session.endSession()
})
}
})
.catch((err) => {
next(err)
})
} else {
//there is no price in GetPrice
const error = new myError(
'There is no price in GetPrice',
400,
5,
'قیمت معادل ریالی پیدا نشد.',
'خطا رخ داد'
)
next(error)
}
})
.catch((err) => {
next(err)
})
})
)
walletRoutes.post('/sellCurrency',
isAuthorized,
userValidationRules('body', 'currency'),
validate,
tryCatch(async (req, res, next) => {
const userId = req.session.userId
const currency = req.body.currency
const quantity = req.body.quantity
GetPrice.findOne({ $and: [{ currency: currency }, { userId: userId }] })
.then((priceObj) => {
if(priceObj && priceObj.currency.toString() === currency.toString() && priceObj.userId.toString() === userId.toString()) {
redis.hashGetAll("RIAL")
.then(async(rialObject: any) => {
if(Boolean(process.env.BUYFROMOFFERS)) {
let errorCounter = 5;
retry(async bail => {
const session = await mongoose.startSession()
let offerIds = []
const suggestedOffers = await suggestOffers({ userId, price: priceObj.rialPricePerUnit, capacity: quantity, offerType: 'buy', currencyId: currency, rialId: rialObject.id })
if(suggestedOffers && Array.isArray(suggestedOffers.subset)) {
offerIds = await suggestedOffers.subset.map((e) => {
return e.id
})
} else {
throw 'No suggested offers!'
}
return session.withTransaction(async() => {
return Active_Offers.find({}).session(session)
.then((offers) => {
if(offers.length==offerIds.length){
console.log("testing")
} else {
return User.findOne({ _id : userId }).session(session)
.then(async (buyerUser: any) => {
return Admin.findOne({}).session(session)
.then(async (admin: any) => {
let givenObjInBuyer = _.find(buyerUser.wallet, (e) => e.currency.toString() === priceObj.currency.toString())
let takenObjInBuyer = _.find(buyerUser.wallet, (e) => e.currency.toString() === rialObject.id.toString())
if(!takenObjInBuyer) {
const takenCurrencyValueObject = {
currency : rialObject.id,
value : 0
}
buyerUser.wallet.push(takenCurrencyValueObject)
takenObjInBuyer = takenCurrencyValueObject
}
const orderAcceptor = offers.map((individualOffer) => {
const buyOrderId = uuidv4()
const sellerId = individualOffer.userId
return User.findOne({_id : sellerId}).session(session)
.then(async (creator: any) => {
if(creator && creator._id.toString() === sellerId.toString()) {
let givenObjInCreWal = _.find(creator.wallet, (e) => e.currency.toString() === individualOffer.curGivenId.toString())
if(givenObjInCreWal) {
// take from creator and give to buyer
givenObjInCreWal.commitment -= Number(individualOffer.curGivenVal)
let takenObjInCreWal = _.find(creator.wallet, (i) => i.currency.toString() === individualOffer.curTakenId.toString())
if(takenObjInCreWal) {
takenObjInCreWal.value += individualOffer.curTakenVal
} else {
const currencyObj = {
currency : individualOffer.curTakenId,
value : individualOffer.curTakenVal
}
creator.wallet.push(currencyObj)
takenObjInCreWal = currencyObj
}
takenObjInBuyer.value -= individualOffer.curTakenVal
givenObjInBuyer.value += individualOffer.curGivenVal
const bodyAccOffer = {
acceptor : admin._id,
creator: sellerId,
offerId: individualOffer.offerId,
curGivenId: individualOffer.curGivenId,
curGivenVal: individualOffer.curGivenVal,
curTakenId: individualOffer.curTakenId,
curTakenVal: individualOffer.curTakenVal,
offeredDate: individualOffer.created_at,
expiredDate : individualOffer.expDate,
buyOrderId
}
await creator.save()
await Accepted_Offers.create([bodyAccOffer], { session }).catch((err)=>{
throw (err)
})
} else {
const error = new myError(
'creator wallet does not have given object',
400,
5,
'در کیف پول فرشنده ارز مورد نظر پیدا نشد',
'خطا رخ داد'
)
throw(error)
}
}
else {
const error = new myError(
'failed to accept an offer',
400,
5,
'موفق به پذیرش یک آفر نشدیم',
'خطا رخ داد'
)
throw(error)
}
})
.catch((err) => {
throw(err)
})
})
return Promise.all(orderAcceptor)
.then(async()=> {
await buyerUser.save()
})
.catch((err) => {
throw(err)
})
})
.catch((err)=>{
throw(err)
})
})
.catch((err) => {
throw(err)
})
}
})
.catch((err) => {
throw (err)
})
})
.then(() => {
successRes(res, 'Buying process finished successfully', true, {})
})
.catch((err) => {
errorCounter--
if(errorCounter==0){
next(err)
} else {
throw(err)
}
})
.finally(() => {
session.endSession()
})
}, {
maxTimeout: 5000,
retries: 5
}
)
} else {
const session = await mongoose.startSession()
session.withTransaction(async() => {
return User.findOne({ _id : userId }).session(session)
.then(async (user: any) => {
let currencyWalInUser = _.find(user.wallet, (i) => { return i.currency.toString() === priceObj.currency.toString() })
if(currencyWalInUser.value >= priceObj.quantity){
return Admin.findOne({}).session(session)
.then(async (admin: any) => {
let rialWalInAdmin = _.find(admin.wallet, (i) => { return i.currency.toString() === rialObject.id.toString() })
let curWalInUser = _.find(user.wallet, (i) => { return i.currency.toString() === currency.toString() })
let rialWalInUser = _.find(user.wallet, (i) => { return i.currency.toString() === rialObject.id.toString() })
let curWalInAdmin = _.find(admin.wallet, (i) => { return i.currency.toString() === currency.toString() })
if(rialWalInUser ) {
rialWalInUser.value += Number(priceObj.rialPrice)
curWalInUser.value -= Number(priceObj.quantity)
curWalInAdmin.value += Number(priceObj.quantity)
rialWalInAdmin.value -= Number(priceObj.rialPrice)
await admin.save()
await user.save()
await priceObj.remove()
} else {
const bodyCurrency = {
currency : currency,
value : priceObj.quantity,
commitment : 0
}
user.wallet.push(bodyCurrency)
rialWalInUser.value -= priceObj.rialPrice
rialWalInAdmin.value += priceObj.rialPrice
curWalInAdmin.value -=priceObj.quantity
await admin.save()
await user.save()
await priceObj.remove()
}
})
} else {
const error = new myError(
'User does not have enough rial credit in his/her wallet.',
400,
5,
'کاربر ارز ریالی کافی برای خرید را ندارد.',
'خطا رخ داد'
)
throw(error)
}
})
.catch((err) => {
throw(err)
})
})
.then(() => {
successRes(res, 'Buying process finished successfully', true, {})
})
.catch((err) => {
next(err)
})
.finally(() => {
session.endSession()
})
}
})
.catch((err)=>{
next(err)
})
} else {
const error = new myError(
'There is no price in GetPrice',
400,
5,
'قیمت معادل ریالی پیدا نشد.',
'خطا رخ داد'
)
next(error)
}
})
.catch((err) => {
next(err)
})
})
)
walletRoutes.post('/transferFromExchange',
isAuthorized,
tryCatch((req, res, next) => {
const value = req.body.value
const currencyId = req.body.currencyId
const receiver = req.body.receiver
const userId = req.session.userId
transferFromExchangeApi(currencyId, value, receiver, userId)
.then((data) => {
successRes(res,"transaction completed please wait",data.txHash)
})
.catch((err)=>{
next(err)
})
}))
walletRoutes.post('/transferToExchange',
tryCatch((req, res, next) => {
const signedRawTxHex = req.body.tx
const currencyId = req.body.currencyId
const userId = req.session.userId
const value = req.body.value
User.findOne({ _id : userId })
.then((user) => {
if(user) {
let cur = _.find(user.wallet, (i) => { return i.currency.toString() === currencyId.toString() })
if(cur) {
//invoke transferToExchangeApi
} else{
const error = new myError(
'currency not found in user wallet',
400,
5,
'ارز مربوطه در کیف پول کاربر پیدا نشد.',
'خطا رخ داد'
)
next(error)
}
} else {
const error = new myError(
'user not found',
400,
5,
'کاربر پیدا نشد.',
'خطا رخ داد'
)
next(error)
}
})
.catch((err)=>{
next(err)
})
}))
walletRoutes.post('/transferToExchangeById',
isAuthorized,
tryCatch((req, res, next) => {
const txId = req.body.txId
const currencyId = req.body.currencyId
const userId = req.session.userId
transferToExchangeByIdApi(currencyId,txId,userId)
.then((txInf: any)=>{
if(txInf.status === "successful") {
successRes(res,"you have the currency")
} else if(txInf.status === "pending") {
successRes(res,"please wait")
}
})
.catch((err)=>{
next(err)
})
}))