Search code examples
ethereumsolidityerc20

Transfering ERC20 Tokens from DApp/Smart Contract to user on a Node.js server


I have a custom token I've sent to my DApp, and I am trying to send some amount of 'rewards' to my users for posting things on the website. However, it seems like nothing is being sent from my backend server/DApp to the users. I'm wondering what I might be doing wrong here. Code below:

server.js

(Basic idea - call approve then transferFrom)

app.post('/send-TOKEN-to-addr', async (req, res) => {

    const post_addr = req.body.addr; // the client sends in user's addr
    var transfer_amt = 5000; // token reward to user
    

    try {
        console.log('send-TOKEN-to-addr gets called for :: '+String(post_addr));
        TOKEN_Contract.methods.approve(DAppAddr, regular_post_transfer_amt).call((err, result) => { 
            console.log('approve:: '+String(result));
            //return res.send(result);
        });
        TOKEN_Contract.methods.transferFrom(DAppAddr, post_addr, transfer_amt).call((err, result) => { 
            //console.log(result);
            return res.send(result);
        });
    
    } catch (e) { throw e; }
});

On the backend I get:

send-TOKEN-to-addr gets called for :: 0xb65ec054bd7f633efd8bd0b59531de464046a7c0

approve:: true

But on the frontend I get no response. As well, when I check the balances of TOKEN for the DApp and the addr, nothing changes, so I think nothing happens here.

I am seeking advice on getting my DApp to send the tokens it has to other addresses. I confirmed that the DApp has the tokens already, I just can't seem to send on behalf of it within my node.js framework.

Edit 1

I have some basic functionality already working with my token (within the DApp), such as the below call:

app.post('/balanceOf-TOKEN-by-addr', async (req, res) => {

    //console.log('balanceOf-TOKEN-by-addr - server');
    const post_addr = req.body.addr;
    //console.log(post_addr);

    try {
        
        TOKEN_Contract.methods.balanceOf(post_addr).call((err, result) => { 
            //console.log(result);
            return res.send(result);
        });
    
    } catch (e) { throw e; }
});

Edit 2

Adding code for how I initialize my DApp - I will need the private keys to call send() methods from it? Because my DApp has a bunch of the TOKENs that I want to send out.

const WEB3_PROVIDER = "HTTP://127.0.0.1:7545"
if (typeof web3 !== 'undefined') {
    web3 = new Web3(web3.currentProvider);
    console.log("web3 already initialized.");
} else {
    // set the provider you want from Web3.providers
    web3 = new Web3(new Web3.providers.HttpProvider(WEB3_PROVIDER));
    console.log("New web3 object initialized.");
}

const DAppABIFile = require('./assets/abis/DAppABI'); 
const DAppABI = DAppABIFile.DAppABI;
const DAppAddr = "0x5C7704a050286D742............"; // public key
const DAppContract = new web3.eth.Contract(DAppABI, DAppAddr);

Solution

  • There's a difference between a call (read-only) and a transaction (read-write).

    Your snippet only calls the contract but doesn't send transactions. So the contract is not able to write state changes from just the calls.

    TOKEN_Contract.methods.<method>.call((err, result) => {});
    

    Web3 uses the .send() function (docs) to send a transaction.

    TOKEN_Contract.methods.<method>.send({
        from: '0x<sender_address>'
    }, (err, result) => {});
    

    Note that the transaction needs to be signed with the private key of the from address. This can be done in 2 ways:

    1. The node provider (most likely passed in the new Web3(<provider_url>) constructor) knows the private key to the sender address and has its account unlocked - this is usually done only on local networks used for development.

    2. You have passed the sender private key to your web3 instance using the wallet.add() function (docs). Then it gets signed in your JS app, and the payload sent to the node in the background contains only the signed transaction (not the private key).

    You can also set this address as the defaultAccount (docs) so that you don't have to keep passing it to the .send() function.