Cómo transferir tokens ERC20 usando web3js

He leído algunos documentos, ejecuté la paridad en el contenedor de la ventana acoplable y pude conectarme y obtener el saldo del token de una cuenta

const Web3 = require('web3'),
const web3 = new Web3(new Web3.providers.HttpProvider('http://127.0.0.1:8545'));
const contract = new web3.eth.Contract(JSON.parse(config.contract.abi), config.contract.address, { from: '0x...', gas: 100000 });

contract.methods.balanceOf('0x...').call()
    .then(console.log)
    .catch(console.error);

Lo que no entiendo es cómo iniciar la transferencia de los tokens.

Según los documentos, debería ser algo como esto:

contract.methods.transfer('0x...', web3.utils.toWei(0.001)).send()
    .then(console.log)
    .catch(console.error);

Lo intenté con devoluciones de llamada, promesas, intenté escuchar eventos, pero en cualquier caso, mi secuencia de comandos se atasca y no sale ni produce ningún resultado.

Estoy esperando un error: no autorizado en este caso y luego averiguaría cómo pasar mi clave privada o desbloquear una cuenta (no estoy muy seguro). No se pudo encontrar en ninguna parte un ejemplo completo.

Todo lo que encontré en stackexchange se parece a

contract.transfer.sendTransaction('0x...', amount, { from: ..., gas: 100000 })

y no funciona para mí en absoluto, se queja de métodos indefinidos: transferir y luego enviarTransacción si usocontract.methods.transfer

Entonces, ¿cuál es la forma correcta de hacer eso?

Respuestas (4)

Casi todos los documentos que existen se refieren a la rama 0.20 de web3.js, que es la rama estable actual. La versión con promesas como la anterior es la rama 1.0.0-beta, que desde hace unos días npm se instala de manera predeterminada.

En cuanto al código de muestra, el siguiente código, casi idéntico al suyo, funciona correctamente para mí desde la consola del nodo en comparación con mi propio token ERC20 que se ejecuta con testrpc localmente.

Creo que el principal punto de diferencia es solo la cantidad transferida. El método toWei() no es realmente aplicable a los tokens ERC20 y puede confundir las cosas.

Además, si está en la red principal, tomará un tiempo para que se extraiga el método send() y la promesa no se resolverá hasta entonces. Es posible que deba configurar la opción gasPrice en el contrato en unos pocos GWei. Pensándolo bien, esto es definitivamente un problema. (Los métodos call() se procesan localmente, por lo que no hay espera ni costo de gasolina).

Pero entonces, si no te está pidiendo que desbloquees una cuenta, no sé... Puede valer la pena configurar testrpc localmente ( testrpc -des útil) e implementar un contrato ERC20 localmente para verificar todo sin las complicaciones adicionales de pagar para gasolina etc

De todos modos, tal vez esta muestra sea útil para usted o para otros.

> const Web3 = require('web3');
undefined
> const web3 = new Web3('http://localhost:8545');
undefined
> web3.version
'1.0.0-beta.11'
> fs = require('fs');
<blah>
> const abi = fs.readFileSync('erc20_abi.json', 'utf-8');
undefined
> const contract = new web3.eth.Contract(JSON.parse(abi), '0xafb7b8a4d90c2df4ce640338029d54a55bedcfc4', { from: '0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1', gas: 100000});
undefined
> contract.methods.balanceOf('0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1').call().then(console.log).catch(console.error);
Promise {...}
> 99997
> contract.methods.transfer('0xffcf8fdee72ac11b5c542428b35eef5769c409f0', 1).send().then(console.log).catch(console.error);
Promise {...}
> { transactionHash: '0xf2d621ba5029a086e212d87fab83be31c3fa41fe47198c378f55c252e5b25d5b',
  transactionIndex: 0,
  blockHash: '0x0e806bf3e88f9335ee9be903303a2393c940ab22f4a73c7e28ca8d9a212ffa4e',
  blockNumber: 429,
  gasUsed: 35206,
  cumulativeGasUsed: 35206,
  contractAddress: null,
  events: 
   { Transfer: 
      { logIndex: 0,
        transactionIndex: 0,
        transactionHash: '0xf2d621ba5029a086e212d87fab83be31c3fa41fe47198c378f55c252e5b25d5b',
        blockHash: '0x0e806bf3e88f9335ee9be903303a2393c940ab22f4a73c7e28ca8d9a212ffa4e',
        blockNumber: 429,
        address: '0xAFB7b8A4d90C2Df4ce640338029d54A55BEDcfC4',
        type: 'mined',
        id: 'log_bfd297b0',
        returnValues: [Object],
        event: 'Transfer',
        signature: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
        raw: [Object] } } }
> contract.methods.balanceOf('0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1').call().then(console.log).catch(console.error);
Promise {...}
> 99996
Esto es genial, así que estoy en el camino correcto. Sin embargo, todavía no puedo ver en su ejemplo cómo autentica/desbloquea una cuenta. Soy consciente de que toWei no es para tokens, pero funciona muy bien con el que estoy probando.
Para ser justos, esa no era su pregunta original... Las cuentas están desbloqueadas con web3.eth.personal.unlockAccount(account, password, unlocktime)las que aún no han llegado a documentarse. No sé cómo esto con un Ledger Nano S, digamos, probablemente a través de la GUI primero. Lo investigaré el fin de semana y actualizaré la respuesta con cualquier cosa útil.

Cambio de API web3 1.0.

let myContract = new web3.eth.Contract(abi, contractAddress);
let data = myContract.methods.transfer(toAddress, value).encodeABI();
let rawTx = {
    "nonce": web3.utils.toHex(nonce),
    "gasPrice": "0x3b9aca00",
    "gasLimit": web3.utils.toHex(gasLimit),
    "to": contractAddress,
    "value": "0x00",
    "data": data,
}
const tx = new Tx(rawTx)
tx.sign(privateKey)
let serializedTx = "0x" + tx.serialize().toString('hex');
web3.eth.sendSignedTransaction(serializedTx).on('transactionHash', function (txHash) {

}).on('receipt', function (receipt) {
    console.log("receipt:" + receipt);
}).on('confirmation', function (confirmationNumber, receipt) {
    //console.log("confirmationNumber:" + confirmationNumber + " receipt:" + receipt);
}).on('error', function (error) {

});

puede ver esto: Transferencia de tokens ERC20 de la cuenta usando web3 sobre Ropsten

Para complementar su respuesta, la transacción también se puede firmar con la instancia web3 de esta manera: let signedTx = web3.eth.accounts.signTransaction(rawTx, privateKey)y luegoweb3.eth.sendSignedTransaction(signedTx.rawTransaction)

Para cualquier otra persona que tenga dificultades, este es un código que funciona completamente con web3 1.0.0-beta.51

Las otras soluciones en esta página no funcionaron para mí.

La "dirección del contrato" es el contrato de token maestro, el que es propiedad del equipo que ejecuta el proyecto. En mi ejemplo, es HST y su dirección de contrato es https://etherscan.io/token/0x554c20b7c486beee439277b4540a434566dc4c02

const Web3 = require('web3')
const Tx = require('ethereumjs-tx')

const Web3js = new Web3(new Web3.providers.HttpProvider('https://mainnet.infura.io/v3/YOUR_PROJECT_ID'))

let tokenAddress = '0x554c20b7c486beee439277b4540a434566dc4c02' // HST contract address
let toAddress = '' // where to send it
let fromAddress = '' // your wallet
let privateKey = Buffer.from('PRIVATE_KEY', 'hex')

let contractABI = [
  // transfer
  {
    'constant': false,
    'inputs': [
      {
        'name': '_to',
        'type': 'address'
      },
      {
        'name': '_value',
        'type': 'uint256'
      }
    ],
    'name': 'transfer',
    'outputs': [
      {
        'name': '',
        'type': 'bool'
      }
    ],
    'type': 'function'
  }
]

let contract = new Web3js.eth.Contract(contractABI, tokenAddress, {from: fromAddress})

// 1e18 === 1 HST
let amount = Web3js.utils.toHex(1e18)

Web3js.eth.getTransactionCount(fromAddress)
  .then((count) => {
    let rawTransaction = {
      'from': fromAddress,
      'gasPrice': Web3js.utils.toHex(20 * 1e9),
      'gasLimit': Web3js.utils.toHex(210000),
      'to': tokenAddress,
      'value': 0x0,
      'data': contract.methods.transfer(toAddress, amount).encodeABI(),
      'nonce': Web3js.utils.toHex(count)
    }
    let transaction = new Tx(rawTransaction)
    transaction.sign(privateKey)
    Web3js.eth.sendSignedTransaction('0x' + transaction.serialize().toString('hex'))
      .on('transactionHash', console.log)
  })

Para transferir, consulte el código a continuación: (reemplace privateKey con el token de la cuenta que se deducirá).

let data = myContract.methods.transfer(dest, amt).encodeABI();
let txObj = {
    gas: config.gas,
    gasPrice: config.gasPrice,
    "to": config.tusdContract,
    "value": "0x00",
    "data": data,
}
web3.eth.accounts.signTransaction(txObj, privateKey, (err, signedTx) => {
if (err) {
    return callback(err);
} else {
    console.log(signedTx);
    return web3.eth.sendSignedTransaction(signedTx.rawTransaction, (err, res) => {
    if (err) {
        console.log(err)
    } else {
        console.log(res);
    }
  });
}
});