Wie sende ich ERC20-Token mit der Web3-API?

Ich habe einige benutzerdefinierte Token im Ropsten-Testnetz mithilfe dieser Anleitung erstellt: https://steemit.com/ethereum/@maxnachamkin/how-to-create-your-own-ethereum-token-in-an-hour-erc20-verified

Ich kann es mit MetaMask an andere Konten senden, kann aber nicht herausfinden, wie es in node.js mit web3, ethereumjs-tx und der Web3-JavaScript-App-API geht.

Mein Code sieht im Moment so aus:

var count = web3.eth.getTransactionCount("0x26...");
var abiArray = JSON.parse(fs.readFileSync('mycoin.json', 'utf-8'));
var contractAddress = "0x8...";
var contract = web3.eth.contract(abiArray).at(contractAddress);
var rawTransaction = {
    "from": "0x26...",
    "nonce": web3.toHex(count),
    "gasPrice": "0x04e3b29200",
    "gasLimit": "0x7458",
    "to": contractAddress,
    "value": "0x0",
    "data": contract.transfer("0xCb...", 10, {from: "0x26..."}),
    "chainId": 0x03
};

var privKey = new Buffer('fc3...', 'hex');
var tx = new Tx(rawTransaction);

tx.sign(privKey);
var serializedTx = tx.serialize();

web3.eth.sendRawTransaction('0x' + serializedTx.toString('hex'), function(err, hash) {
    if (!err)
        console.log(hash);
    else
        console.log(err);
});

In diesem Fall stoppt der Code nur bei contract.transfer("0xCb...", 10, {from: "0x26..."})einem Teil und die Anfrage steht nur noch aus. Konnte keine Anleitung finden, um ähnliche Dinge zu tun. Habe hier Code gefunden:

Web3 sendet benutzerdefinierte Token mithilfe der Übertragungsfunktion. Sie müssen das Von-Konto festlegen

Und hier:

So übertragen Sie ERC20-Token mit web3js

Aber immer noch stecken geblieben, weiß nicht, was ich vermisse.

Wenn Sie sich die web3-Dokumentation ansehen, können Sie contract.methods ausprobieren. transfer("0xCb...", 10).send({from : XX},function(){dostuff})anstatt sendRawTransaction zu senden.
Sie sollten verwenden getData, um die rohen Transaktionsdaten wie folgt zu generieren "data": contract.transfer.getData("0xCb...", 10, {from: "0x26..."}),.
Diese Antwort erklärt, wie man getData ethereum.stackexchange.com/a/12932 verwendet
@TomasNavickas hast du web3.js verwendet v0.20.x? Können Sie bitte ein vollständiges Beispiel als Antwort posten und als gelöst markieren. Vielen Dank.
@TomasNavickas Das funktioniert, aber es sendet wei (Ether) anstelle des eigentlichen Tokens. Irgendeine Idee warum?
@Viper Wei (Ether) wird verwendet, um die Transaktionsgebühr zu bezahlen. Können Sie ein Transaktionsbeispiel angeben, damit wir sehen können, welche Eingabedaten Sie senden?
@TomasNavickas Ich habe es hier als neue Frage hinzugefügt, danke! ethereum.stackexchange.com/questions/29513/…
Was macht der Buffer in diesem Fall? Warum wird die var privKey nicht einfach als String zugewiesen, der den privaten Schlüssel enthält?

Antworten (6)

Ich verwende die web3.js-Version: 0.20.1 in der node.js-Express-Anwendung. Ich verwende Parity auf einem Virtualbox-Ubuntu-Computer.

Der richtige Code sieht wie folgt aus:

var count = web3.eth.getTransactionCount("0x26...");
var abiArray = JSON.parse(fs.readFileSync('mycoin.json', 'utf-8'));
var contractAddress = "0x8...";
var contract = web3.eth.contract(abiArray).at(contractAddress);
var rawTransaction = {
    "from": "0x26...",
    "nonce": web3.toHex(count),
    "gasPrice": "0x04e3b29200",
    "gasLimit": "0x7458",
    "to": contractAddress,
    "value": "0x0",
    "data": contract.transfer.getData("0xCb...", 10, {from: "0x26..."}),
    "chainId": 0x03
};

var privKey = new Buffer('fc3...', 'hex');
var tx = new Tx(rawTransaction);

tx.sign(privKey);
var serializedTx = tx.serialize();

web3.eth.sendRawTransaction('0x' + serializedTx.toString('hex'), function(err, hash) {
    if (!err)
        console.log(hash);
    else
        console.log(err);
});
Können Sie bitte das Tutorial zur Verwendung von web3.js für Ether- und Token-Transfers teilen?
Was macht der Buffer in diesem Fall? Warum wird die var privKey nicht einfach als String zugewiesen, der den privaten Schlüssel enthält?
Ich könnte mir vorstellen, dass ein generisches IERC20 ABI verwendet werden kann?

Ich fand viele Antworten, die veraltet waren oder denen wichtige Informationen fehlten. Hier ist, was endlich für mich in meinem node.js-Projekt mit der Web3-Version 1.0.0-beta.26 funktioniert hat. Beachten Sie, dass dies für das Ethereum-Hauptnetz gilt. Um das Robsten-Testnetz zu verwenden, ändern Sie die chainId in 0x03

// Get private stuff from my .env file
import {my_privkey, infura_api_key} from '../.env'

// Need access to my path and file system
import path from 'path'
var fs = require('fs');

// Ethereum javascript libraries needed
import Web3 from 'Web3'
var Tx = require('ethereumjs-tx');

// Rather than using a local copy of geth, interact with the ethereum blockchain via infura.io
const web3 = new Web3(Web3.givenProvider || `https://mainnet.infura.io/` + infura_api_key)

// Create an async function so I can use the "await" keyword to wait for things to finish
const main = async () => {
  // This code was written and tested using web3 version 1.0.0-beta.26
  console.log(`web3 version: ${web3.version}`)

  // Who holds the token now?
  var myAddress = "0x97...";

  // Who are we trying to send this token to?
  var destAddress = "0x4f...";

  // If your token is divisible to 8 decimal places, 42 = 0.00000042 of your token
  var transferAmount = 1;

  // Determine the nonce
  var count = await web3.eth.getTransactionCount(myAddress);
  console.log(`num transactions so far: ${count}`);

  // This file is just JSON stolen from the contract page on etherscan.io under "Contract ABI"
  var abiArray = JSON.parse(fs.readFileSync(path.resolve(__dirname, './tt3.json'), 'utf-8'));

  // This is the address of the contract which created the ERC20 token
  var contractAddress = "0xe6...";
  var contract = new web3.eth.Contract(abiArray, contractAddress, { from: myAddress });

  // How many tokens do I have before sending?
  var balance = await contract.methods.balanceOf(myAddress).call();
  console.log(`Balance before send: ${balance}`);

  // I chose gas price and gas limit based on what ethereum wallet was recommending for a similar transaction. You may need to change the gas price!
  var rawTransaction = {
      "from": myAddress,
      "nonce": "0x" + count.toString(16),
      "gasPrice": "0x003B9ACA00",
      "gasLimit": "0x250CA",
      "to": contractAddress,
      "value": "0x0",
      "data": contract.methods.transfer(destAddress, transferAmount).encodeABI(),
      "chainId": 0x01
  };

  // Example private key (do not use): 'e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109'
  // The private key must be for myAddress
  var privKey = new Buffer(my_privkey, 'hex');
  var tx = new Tx(rawTransaction);
  tx.sign(privKey);
  var serializedTx = tx.serialize();

  // Comment out these three lines if you don't really want to send the TX right now
  console.log(`Attempting to send signed tx:  ${serializedTx.toString('hex')}`);
  var receipt = await web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex'));
  console.log(`Receipt info:  ${JSON.stringify(receipt, null, '\t')}`);

  // The balance may not be updated yet, but let's check
  balance = await contract.methods.balanceOf(myAddress).call();
  console.log(`Balance after send: ${balance}`);
}

main();

Beachten Sie, dass das Senden manchmal "fehlschlägt", weil der TX nicht innerhalb von 50 Blöcken abgebaut wurde. In meiner Kopie des web3-Quellcodes habe ich TIMEOUTBLOCK von 50 auf 500 geändert, damit ich mich nicht damit befassen muss.

Beachten Sie, dass dieser Code keine Fehlerbehandlung hat - Sie möchten vielleicht welche hinzufügen.

Man müsste also den Abi Json für jeden einzelnen ERC-20, der gehandhabt werden muss, in die Hand nehmen und verwalten?
Oder ist es möglich, einen „Standard-ERC20-Token-ABI“ zu verwenden, wie dieses Modul vorschlägt? github.com/danfinlay/human-standard-token-abi
Um eine Funktion für einen beliebigen Vertrag aufzurufen, benötigen Sie lediglich die Vertragsadresse und die Signatur der Funktion. ERC20 definiert mehrere Funktionen. Solange der Vertrag an der Adresse eine öffentliche Funktion mit der gleichen Signatur definiert wie diejenige, die Sie anrufen, können Sie sie aufrufen.
Ich erhalte diesen Fehler: Fehler: Zurückgegebener Fehler: ungültiger Absender
@dacoinminster, wenn ich Ether sende, funktioniert es, aber wenn ich das Token sende, wird ein Fehler ausgegeben. Fehler: Zurückgegebener Fehler: ungültiger Absender. Mache ich etwas falsch?
Verwenden Sie für Testnet (ropsnet) chainId: hex(3)
können Sie eine Zufallszahl für die Nonce verwenden?
@zero_cool nein, es muss größer sein als die vorherige Nonce. Deshalb rufen wir an getTransactionCount.
Ich erhalte eine leere Empfangsbestätigung und meine Token werden nicht übertragen. Ich erhalte die Fehlermeldung „Die Transaktion wurde nicht innerhalb von 750 Sekunden abgebaut. Bitte stellen Sie sicher, dass Ihre Transaktion ordnungsgemäß gesendet wurde. Seien Sie sich bewusst, dass sie möglicherweise noch abgebaut wird!“ nach 15 minuten was tun?
Was macht der Buffer in diesem Fall? Warum wird die var privKey nicht einfach als String zugewiesen, der den privaten Schlüssel enthält?

Wenn wir nur eine Transaktion mit der erc20-Methode senden möchten transfer, können wir ein Vertragsobjekt erstellen, indem wir minABI und die Vertragsadresse verwenden, z. B. den folgenden Code:

let minABI = [
// transfer
{
    "constant": false,
    "inputs": [
        {
            "name": "_to",
            "type": "address"
        },
        {
            "name": "_value",
            "type": "uint256"
        }
    ],
    "name": "transfer",
    "outputs": [
        {
            "name": "success",
            "type": "bool"
        }
    ],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
}
];
let contractAddres="put the erc20 contract address here";

let contract = await new web3.eth.Contract(minABI, contractAddr);

contract.methods.transfer("your account", "amount of erc20 tokens you want transfer").send({
        from: "your account"
    });

und das ist Arbeit für mich, hoffe, das wird helfen. :D

Danke, das ist besser als die anderen Antworten, Sie brauchen und haben normalerweise nicht die ABI eines öffentlichen ERC20-Tokens.

Sie können dieses Arbeitsbeispiel mit meinem Token überprüfen: https://github.com/religion-counter/onlyone/blob/main/helper-scripts/send-onlyone.js

Sie können auch zum Repo beitragen, wenn Sie interessiert sind.

// Helper script that sends ONLYONE token to target addresses specified in targets.txt
// Target index - index in targets.txt file is specified as an argument - process.argv.splice(2)[0]

var fs = require('fs')

var targetAccounts = JSON.parse(fs.readFileSync('targets.txt', 'utf-8'));

var myAddress = JSON.parse(fs.readFileSync("my-address.json", 'utf-8'));
var targetIndex = Number(process.argv.splice(2)[0]);

console.log(`Sending ONLYONE to target ${targetIndex}.`);

async function sendOnlyone(fromAddress, toAddress) {

    var Tx = require('ethereumjs-tx').Transaction;
    var Web3 = require('web3');
    var web3 = new Web3(new Web3.providers.HttpProvider('https://bsc-dataseed.binance.org/'));

    var amount = web3.utils.toHex(10);
    var privateKey = Buffer.from(myAddress.privateKey, 'hex');
    var abiArray = JSON.parse(JSON.parse(fs.readFileSync('onlyone-abi.json','utf-8')));
    var contractAddress = '0xb899db682e6d6164d885ff67c1e676141deaaa40'; // ONLYONE address
    var contract = new web3.eth.Contract(abiArray, contractAddress, {from: fromAddress});
    var Common = require('ethereumjs-common').default;
    var BSC_FORK = Common.forCustomChain(
        'mainnet',
        {
        name: 'Binance Smart Chain Mainnet',
        networkId: 56,
        chainId: 56,
        url: 'https://bsc-dataseed.binance.org/'
        },
        'istanbul',
    );

    var count = await web3.eth.getTransactionCount(myAddress);

    var rawTransaction = {
        "from":myAddress,
        "gasPrice":web3.utils.toHex(5000000000),
        "gasLimit":web3.utils.toHex(210000),
        "to":contractAddress,"value":"0x0",
        "data":contract.methods.transfer(toAddress, amount).encodeABI(),
        "nonce":web3.utils.toHex(count)
    };

    var transaction = new Tx(rawTransaction, {'common':BSC_FORK});
    transaction.sign(privateKey)

    var result = await web3.eth.sendSignedTransaction('0x' + transaction.serialize().toString('hex'));
    return result;
}

sendOnlyone(myAddress, targetAccounts[targetIndex]);

Bin auf diese Dokumentation in einem der Projekte gestoßen, die ich in meiner Dapp verwende: https://developers.fortmatic.com/docs/smart-contract-functions

Die Dokumentation erklärt ziemlich ausführlich, was Schritt für Schritt zu tun ist, um ERC20-Token-Transfers für Web3-Versionen vor 1.0 (0.20.x) und nach 1.0 zu senden.

Hier ist eine Vorschau, wie die Version 0.20.x des web3-Codes aussieht.

// Initialize provider
import Fortmatic from 'fortmatic';
import Web3 from 'web3';

const fm = new Fortmatic('YOUR_API_KEY');
window.web3 = new Web3(fm.getProvider()); // Can replace with MetaMask web3 provider

// Get the contract ABI from compiled smart contract json
const erc20TokenContractAbi = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"tokens","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"tokens","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"withdrawEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"_totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"tokenOwner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"acceptOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"a","type":"uint256"},{"name":"b","type":"uint256"}],"name":"safeSub","outputs":[{"name":"c","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"tokens","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"a","type":"uint256"},{"name":"b","type":"uint256"}],"name":"safeDiv","outputs":[{"name":"c","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"tokens","type":"uint256"},{"name":"data","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"a","type":"uint256"},{"name":"b","type":"uint256"}],"name":"safeMul","outputs":[{"name":"c","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"newOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"tokenAddress","type":"address"},{"name":"tokens","type":"uint256"}],"name":"transferAnyERC20Token","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"tokenOwner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"a","type":"uint256"},{"name":"b","type":"uint256"}],"name":"safeAdd","outputs":[{"name":"c","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"tokens","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"tokenOwner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"tokens","type":"uint256"}],"name":"Approval","type":"event"}];

// Create contract object
const tokenContract = web3.eth.contract(erc20TokenContractAbi);
// Instantiate contract
const tokenContractInstance = tokenContract.at('0x8EBC7785b83506AaA295Bd9174e6A7Ad5681fb80');

const toAddress = '0xE0cef4417a772512E6C95cEf366403839b0D6D6D';
// Calculate contract compatible value for transfer with proper decimal points using BigNumber
const tokenDecimals = web3.toBigNumber(18);
const tokenAmountToTransfer = web3.toBigNumber(100);
const calculatedTransferValue = web3.toHex(tokenAmountToTransfer.mul(web3.toBigNumber(10).pow(tokenDecimals)));

// Call contract function (non-state altering) to get total token supply
tokenContractInstance.totalSupply.call(function(error, result) {
  if (error) throw error;
  console.log(result);
});

// Get user account wallet address first
web3.eth.getAccounts(function(error, accounts) {
  if (error) throw error;
  // Send ERC20 transaction with web3
  tokenContractInstance.transfer.sendTransaction(toAddress, calculatedTransferValue, {from: accounts[0]}, function(error, txnHash) {
    if (error) throw error;
    console.log(txnHash);
  });
});

So geht's mit ethers.js. Ich weiß nicht, warum die meisten anderen Antworten einen Import eines nicht spezifizierten ABI-json verwenden, wenn ERC20 für alle Token dieselbe Schnittstelle verwendet und Sie wahrscheinlich nicht die ABI eines öffentlichen Tokens haben.

Beachten Sie, dass in diesem Beispiel davon ausgegangen wird, dass der Betrag mit einer Potenz von 6 ( ) multipliziert werden muss, utils.parseUnits(n, 6)was beispielsweise für USDT und USDC gilt. Andere ERC20-Token können variieren.

import { Contract, providers, utils } from 'ethers'

const erc20abi = [
  /**
   * @dev Returns the amount of tokens in existence.
   */
  'function totalSupply() external view returns (uint256)',

  /**
   * @dev Returns the amount of tokens owned by `account`.
   */
  'function balanceOf(address account) external view returns (uint256)',

  /**
   * @dev Moves `amount` tokens from the caller's account to `recipient`.
   *
   * Returns a boolean value indicating whether the operation succeeded.
   *
   * Emits a {Transfer} event.
   */
  'function transfer(address recipient, uint256 amount) external returns (bool)',

  /**
   * @dev Returns the remaining number of tokens that `spender` will be
   * allowed to spend on behalf of `owner` through {transferFrom}. This is
   * zero by default.
   *
   * This value changes when {approve} or {transferFrom} are called.
   */
  'function allowance(address owner, address spender) external view returns (uint256)',

  /**
   * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
   *
   * Returns a boolean value indicating whether the operation succeeded.
   *
   * IMPORTANT: Beware that changing an allowance with this method brings the risk
   * that someone may use both the old and the new allowance by unfortunate
   * transaction ordering. One possible solution to mitigate this race
   * condition is to first reduce the spender's allowance to 0 and set the
   * desired value afterwards:
   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
   *
   * Emits an {Approval} event.
   */
  'function approve(address spender, uint256 amount) external returns (bool)',

  /**
   * @dev Moves `amount` tokens from `sender` to `recipient` using the
   * allowance mechanism. `amount` is then deducted from the caller's
   * allowance.
   *
   * Returns a boolean value indicating whether the operation succeeded.
   *
   * Emits a {Transfer} event.
   */
  'function transferFrom(address sender, address recipient, uint256 amount) external returns (bool)',

  /**
   * @dev Emitted when `value` tokens are moved from one account (`from`) to
   * another (`to`).
   *
   * Note that `value` may be zero.
   */
  'event Transfer(address indexed from, address indexed to, uint256 value)',

  /**
   * @dev Emitted when the allowance of a `spender` for an `owner` is set by
   * a call to {approve}. `value` is the new allowance.
   */
  'event Approval(address indexed owner, address indexed spender, uint256 value)',
]

export async function approveERC20(
  provider: providers.Web3Provider,
  tokenAddr: string,
  spenderAddr: string,
  amount: string
) {
  const signer = provider.getSigner()
  const contract = new Contract(tokenAddr, erc20abi, signer)
  contract.approve(spenderAddr, utils.parseUnits(amount, 6))
}

export async function transferERC20(
  provider: providers.Web3Provider,
  tokenAddr: string,
  recipientAddr: string,
  amount: string
) {
  const signer = provider.getSigner()
  const contract = new Contract(tokenAddr, erc20abi, signer)
  contract.transfer(recipientAddr, utils.parseUnits(amount, 6))
}