diff --git a/server/api/controllers/claim.js b/server/api/controllers/claim.js new file mode 100644 index 0000000..2d705f5 --- /dev/null +++ b/server/api/controllers/claim.js @@ -0,0 +1,161 @@ +import ClaimTx from '../models/ClaimTx' +import { wrapAsync } from '../utils' +import sdk from '@linkdrop/binance-sdk' +import Table from 'cli-table' + +const config = require('../../config/config.json') + +/** + * Function to check whether a `linkId` has already been claimed + */ +const isClaimed = wrapAsync(async (req, res) => { + const linkId = req.params.linkId + + // Check whether a claim tx exists in database + const oldClaimTx = await ClaimTx.findOne({ + linkId + }) + + if (oldClaimTx && oldClaimTx.txHash) { + return res.json({ + isClaimed: true, + txHash: oldClaimTx.txHash + }) + } else return res.json({ isClaimed: false }) +}) + +/** + * Helper function to transfer funds + * @param {String} privateKey Private key to send funds from + * @param {String} to Address to send funds to + * @param {String} asset Asset symbol + * @param {String} amount Asset amount in decimal form, e.g. 1.00000000 + * @param {String} memo An optional message to send along with funds + * @return {Promise) `{success, txHash, error}` + */ +const transfer = async ({ privateKey, to, asset, amount, memo }) => { + try { + const from = sdk.utils.getAddressFromPrivateKey(privateKey) + const sequence = await sdk.utils.getSequence(from) + const bncClient = await sdk.utils.initBncClient(privateKey) + + const result = await bncClient.transfer( + from, + to, + amount, + asset, + memo, + sequence + ) + + if (result.status === 200) { + return { success: true, txHash: result.result[0].hash } + } else { + return { success: false, error: result } + } + } catch (err) { + throw new Error(err) + } +} + +const claim = wrapAsync(async (req, res) => { + const { + asset, + amount, + linkId, + verifierSignature, + receiverAddress, + receiverSignature + } = req.body + + const VERIFIER_ADDRESS = + process.env.VERIFIER_ADDRESS || config.VERIFIER_ADDRESS + const SENDER_PRIVATE_KEY = + process.env.SENDER_PRIVATE_KEY || config.SENDER_PRIVATE_KEY + const senderAddress = sdk.utils.getAddressFromPrivateKey(SENDER_PRIVATE_KEY) + + const table = new Table() + + // Check whether a claim tx exists in database + const oldClaimTx = await ClaimTx.findOne({ + linkId + }) + + if (oldClaimTx && oldClaimTx.txHash) { + table.push(['Tx hash', oldClaimTx.toObject().txHash]) + + console.log('\n✅ Submitted claim transaction') + console.log(table.toString(), '\n') + + return res.json({ + success: true, + txHash: oldClaimTx.txHash + }) + } else { + if ( + (await LinkdropSDK.binance.checkLinkParams({ + asset, + amount, + linkId, + verifierAddress: VERIFIER_ADDRESS, + verifierSignature, + receiverAddress, + receiverSignature, + senderAddress + })) === true + ) { + try { + const memo = `Linkdrop Id: ${linkId}` + + const { success, txHash, error } = await transfer({ + privateKey: SENDER_PRIVATE_KEY, + to: receiverAddress, + asset, + amount: sdk.utils.formatUnits(Number(amount)), + memo + }) + + if (success === true && txHash) { + // Save claim tx to database + const claimTx = new ClaimTx({ + asset, + amount, + linkId, + senderAddress, + receiverAddress, + verifierAddress: VERIFIER_ADDRESS, + txHash + }) + + const document = await claimTx.save() + + for (let key in claimTx.toObject()) { + if (key !== '_id' && key !== '__v') { + table.push([key, claimTx.toObject()[key]]) + } + } + + console.log('\n✅ Submitted claim transaction') + console.log(table.toString(), '\n') + + asset, + amount, + linkId, + verifierSignature, + receiverAddress, + receiverSignature + } + + res.json({ + success, + txHash, + error + }) + } catch (err) { + console.error(err) + } + } + } +}) + +export default { claim, isClaimed } diff --git a/server/api/models/ClaimTx.js b/server/api/models/ClaimTx.js new file mode 100644 index 0000000..34b36e7 --- /dev/null +++ b/server/api/models/ClaimTx.js @@ -0,0 +1,15 @@ +import { Schema, model } from 'mongoose' + +const claimTxSchema = new Schema({ + asset: { type: String, required: true }, + amount: { type: String, required: true }, + linkId: { type: String, required: true }, + senderAddress: { type: String, required: true }, + receiverAddress: { type: String, required: true }, + verifierAddress: { type: String, required: true }, + txHash: { type: String, reqiured: true } +}) + +const ClaimTx = model('ClaimTx', claimTxSchema) + +export default ClaimTx diff --git a/server/package.json b/server/package.json index eb5f2f3..e270583 100644 --- a/server/package.json +++ b/server/package.json @@ -5,5 +5,30 @@ "main": "index.js", "author": "Amir Jumaniyazov - ", "license": "MIT", - "private": true + "private": true, + "dependencies": { + "@binance-chain/javascript-sdk": "https://github.com/Dobrokhvalov/javascript-sdk", + "@hapi/boom": "^7.4.2", + "@linkdrop/sdk": "https://github.com/LinkdropProtocol/linkdrop-sdk", + "axios": "^0.19.0", + "cli-table": "^0.3.1", + "cors": "^2.8.5", + "csvtojson": "^2.0.8", + "dotenv": "^8.0.0", + "ethers": "^4.0.31", + "express": "^4.17.1", + "fast-csv": "^3.0.1", + "mongoose": "^5.6.0", + "morgan": "^1.9.1", + "pm2": "^3.5.1", + "query-string": "^6.8.1" + }, + "devDependencies": { + "@babel/cli": "^7.4.4", + "@babel/core": "^7.4.5", + "@babel/node": "^7.4.5", + "@babel/polyfill": "^7.4.4", + "@babel/preset-env": "^7.4.5", + "@babel/register": "^7.4.4", + } } \ No newline at end of file