简体   繁体   中英

Sending signed transactions to Ropsten

It's easy to use web3js to call functions that don't require signing (eg functions that do not update the state of a contract). However, it's not clear how to call functions that require signing, other than manually unlocking my MetaMask wallet and calling functions inside Remix environment.

After deploying my dapp for the first time to Ropsten, I need to call createItem(string name, uint price) 100 times to populate the items array initially. Since I don't want to do it manually in Remix, I want to write a script that does it automatically.

It looks like I need to have ethereumjs-tx in addition to web3js to sign transactions programatically without having MetaMask. I also need to have my account and privateKey . With all this information and the official web3js doc, I come up with the following:

// Call an external function programatically

const web3 = new Web3(new Web3.providers.HttpProvider("https://ropsten.infura.io"))
const account = "ACCOUNT_ADDRESS"
const privateKey = new Buffer('PRIVATE_KEY', 'hex')
const contract = new web3.eth.Contract(abi, CONTRACT_ADDRESS, {
  from: account,
  gas: 3000000,
})

const functionAbi = contract.methods.myFunctionName(myArgument).encodeABI()

let estimatedGas
contract.methods.myFunctionNAme(myArgument).estimateGas({
  from: account,
}).then((gasAmount) => {
  estimatedGas = gasAmount.toString(16)
})

const txParams = {
  gasPrice: '0x' + estimatedGas,
  to: CONTRACT_ADDRESS,
  data: functionAbi,
  from: account,
}

const tx = new Tx(txParams)
tx.sign(privateKey)

const serializedTx = tx.serialize()

web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex')).
  on('receipt', console.log)

The code runs, but txParams is actually missing one key: nonce . When you run this, you get the following error:

Unhandled rejection Error: Returned error: nonce too low

Here are my questions:

  1. Is this generally the right way to do what I am trying to do?
  2. If 1 is true, how do I get the nonce parameter for a deployed contract?

References:

  1. http://web3js.readthedocs.io/en/1.0/
  2. https://github.com/ethereumjs/ethereumjs-tx
  3. https://ethereum.stackexchange.com/questions/21402/web3-eth-call-how-can-i-set-data-param
  4. https://ethereum.stackexchange.com/questions/6368/using-web3-to-sign-a-transaction-without-connecting-to-geth

UPDATE:

Thanks to Adam, now I learned how to get the nonce . So I added this following code:

let nonce
web3.eth.getTransactionCount(account).then(_nonce => {
  nonce = _nonce.toString(16)
})

const txParams = {
  gasPrice: '0x' + gasPrice,
  to: CONTRACT_ADDRESS,
  data: functionAbi,
  from: account,
  nonce: '0x' + nonce,
}

But now I keep running into this exception:

Unhandled rejection Error: Returned error: rlp: input string too long for uint64, decoding into (types.Transaction)(types.txdata).AccountNonce

Google search wasn't helpful except for letting me locate this file ( https://github.com/ethereum/go-ethereum/blob/master/rlp/decode.go ) that has the exception handler. Does anyone know how to solve this?

Generally looks correct. The only question I would have is how are you planning on loading the private key? You will either need to prompt for the private key, or import/read in the keystore and prompt for the password. You can use keythereum to accomplish this (See the key import section for example code).

The nonce is just an incremental number used to order transactions for an account. To get the correct value, simply use web3.eth.getTransactionCount(account)

EDIT - Example run using Ganache with locked accounts ( --secure option):

SimpleContract.sol

pragma solidity ^0.4.19;

contract SimpleContract {
  uint public val = 4;

  function increment(uint amt) public {
    val += amt;
  }

  function get() public constant returns (uint) {
    return  val;
  }
}

test.js

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

const provider = new Web3.providers.HttpProvider("http://localhost:8545");
const web3 = new Web3(provider);

const account = '0x9a6d82ef3912d5ab60473124bccd2f2a640769d7'; // Ganache
const privateKey = Buffer.from('70f1384b24df3d2cdaca7974552ec28f055812ca5e4da7a0ccd0ac0f8a4a9b00', 'hex');
const contractAddress = '0x6dd7c1c13df7594c27e0d191fd8cc21efbfc98b4'; // Deployed manually
const abi = [{"constant":true,"inputs":[],"name":"val","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"amt","type":"uint256"}],"name":"increment","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}];

const contract = new web3.eth.Contract(abi, contractAddress, {
  from: account,
  gasLimit: 3000000,
});

const contractFunction = contract.methods.increment(3);

const functionAbi = contractFunction.encodeABI();

let estimatedGas;
let nonce;

console.log("Getting gas estimate");

contractFunction.estimateGas({from: account}).then((gasAmount) => {
  estimatedGas = gasAmount.toString(16);

  console.log("Estimated gas: " + estimatedGas);

  web3.eth.getTransactionCount(account).then(_nonce => {
    nonce = _nonce.toString(16);

    console.log("Nonce: " + nonce);
    const txParams = {
      gasPrice: '0x09184e72a000',
      gasLimit: 3000000,
      to: contractAddress,
      data: functionAbi,
      from: account,
      nonce: '0x' + nonce
    };

    const tx = new Tx(txParams);
    tx.sign(privateKey);

    const serializedTx = tx.serialize();

    contract.methods.get().call().then(v => console.log("Value before increment: " + v));

    web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex')).on('receipt', receipt => {
      console.log(receipt);
      contract.methods.get().call().then(v => console.log("Value after increment: " + v));
    })
  });
});

Results:

$ npm run my_test

> hello_world_dapp@1.0.0 my_test C:\cygwin\home\adamk\eth\hello_world_dapp
> node ./test.js

Getting gas estimate
Estimated gas: 6919
Nonce: 5
Value before increment: 19
{ transactionHash: '0xb6fdfc36cc32cb01a2be8832a387da586a44a37c1241bbf2979745536f206ed4',
  transactionIndex: 0,
  blockHash: '0xb6ee8d4e45585010d9a12d48aa37a478eb6021aad78599f1719cb424ab364bb5',
  blockNumber: 10,
  gasUsed: 26905,
  cumulativeGasUsed: 26905,
  contractAddress: null,
  logs: [],
  status: 1 }
Value after increment: 22

Here is a code-snippet for sending signed transaction on rinkeby.In a similar way,you can proceed for ropsten test network:

const Tx = require('ethereumjs-tx')
const Web3 = require('web3')
const web3 =new 
Web3('https://rinkeby.infura.io/v3/9ce80a86c6c54d22aa821d0486a1a47d')


var account1 = '0xa00c70B72150D627cf53872eefD077079116B6a6'
var account2 = '0xD2a8aa318Fbc56995Db8C415BE6E40329DB1C56C'

const privateKey1 = Buffer.from(process.env.PRIVATE_KEY_1,'hex')
const privateKey2 = Buffer.from(process.env.PRIVATE_KEY_2,'hex')

web3.eth.getTransactionCount(account1,(err,txCount)=>{
const txObject = {
nonce:web3.utils.toHex(txCount),
to:account2,
value:web3.utils.toHex(web3.utils.toWei('0.1','ether')),
gasLimit:web3.utils.toHex(21000),
gasPrice:web3.utils.toHex(web3.utils.toWei('10','gwei')),
}
console.log(txObject)
const tx = new Tx(txObject)
tx.sign(privateKey1)

const serializedTransaction = tx.serialize()
const raw = '0x'+serializedTransaction.toString('hex')

web3.eth.sendSignedTransaction(raw,(err,txHash)=>{
console.log(txHash)
})

})

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM