How to manually create the instance of the contract in truffle - ethereum

Say I have 2 contracts like this
A.sol
import './B.sol';
contract A {
event BCreated(address addressOfB);
function createB(){
B b = new B();
BCreated(b);
}
}
B.sol
contract B {
uint8 value = 5;
function getValue() constant returns(uint8){
return value;
}
}
I am trying to write the test cases for these contracts.
I can deploy the contract A using the migrations file and will
get the instance of it.
But I am not sure about how to get the instance of contract B,
after the contract is created using function createB()
Ok I can get the address of the contract B in events after calling function createB(),
But not sure about the instance.
For this example, you can say that I can separately test contract B as it doesn't do much.
But in the real case, I need to create an instance using the address coming from the event.
Here is the little bit of js code for truffle test file
In this I have the address of B
var A = artifacts.require("./A.sol");
contract('A', (accounts) => {
it("Value should be 5", async () => {
let instanceOfA = await A.deployed()
let resultTx = await instanceOfA.createB({ from: accounts[0] });
console.log("Address of B: " + resultTx.logs[0].args.addressOfB);
/**
* How do I create the instance of B now?
*/
})
})

You can try to do it like this
var A = artifacts.require("./A.sol");
var B = artifacts.require("./B.sol");
contract('A', (accounts) => {
it("Value should be 5", async () => {
let instanceOfA = await A.deployed()
let resultTx = await instanceOfA.createB({ from: accounts[0] });
console.log("Address of B: " + resultTx.logs[0].args.addressOfB);
let instanceOfB = await B.at(resultTx.logs[0].args.addressOfB);
})
})

Related

hardhat & waffle - deploy a contract from an address

I'm trying to test a factory contract using hardhat and waffle. I have a contract called Domain:
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "hardhat/console.sol";
contract Domain {
string private publicKey;
address[] public children;
constructor(string memory _publicKey) {
console.log("Deploying a domain using public key: ", _publicKey);
publicKey = _publicKey;
}
function getChildren() public view returns (address[] memory){
return children;
}
}
And a factory for deploying this contract:
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "./Domain.sol";
import "hardhat/console.sol";
contract DomainFactory {
Domain[] private _domains;
function createDomain(
string memory _publicKey
) public returns (address){
Domain domain = new Domain(
_publicKey
);
_domains.push(domain);
return address(domain);
}
function allDomains(uint256 limit, uint256 offset)
public
view
returns (Domain[] memory coll)
{
return coll;
}
}
I have the following tests defined, where this refers to a context object defined in a "world" file (using cucumber.js.
When('the holder of this public key creates a domain', async function () {
this.domain = await this.factory.createDomain('<public_key>');
});
Then('a child of this domain has the name name', async function () {
const children = this.domain.getChildren();
const childrenWithName = children.find((child:any) => {
return child.getNames().some((childName:any) => {
return childName === 'name';
})
})
expect(childrenWithName).to.be.an('array').that.is.not.empty;
});
Ideally in the when step, I could define this.domain as the result of deploying a contract, and thereafter test the methods of the contract I deploy:
// world.ts
import { setWorldConstructor, setDefaultTimeout } from '#cucumber/cucumber'
import {deployContract, MockProvider, solidity} from 'ethereum-waffle';
import {use} from "chai";
import DomainContract from "../../../artifacts/contracts/Domain.sol/Domain.json";
import DomainFactoryContract from "../../../artifacts/contracts/DomainFactory.sol/DomainFactory.json";
import { Domain, DomainFactory } from "../../../typechain-types";
import {Wallet} from "ethers";
use(solidity);
setDefaultTimeout(20 * 1000);
class DomainWorld {
public owner: string
public wallets: Wallet[]
public factory: DomainFactory | undefined
public domain: Domain | undefined
public ready: boolean = false
private _initialized: Promise<boolean>
async deployContractByAddress(address, ...args){
return await deployContract(this.wallets[0], address, ...args);
}
constructor() {
this.wallets = new MockProvider().getWallets();
this.owner = this.wallets[0].address
const that = this
this._initialized = new Promise(async (resolve, reject) => {
try {
that.factory = (await deployContract(that.wallets[0], DomainFactoryContract, [])) as DomainFactory;
that.ready = true
resolve(true)
}catch (err) {
reject(err)
}
})
}
}
setWorldConstructor(DomainWorld);
My problem is, hardhat's deployContract function isn't expecting a contract address, which is what is returned by my DomainFactory's create method. How can I test contracts deployed via my factory if the return value is an address?
I've made a quick hardhat project for you to test it.
Here are the highlights:
The easiest way I find to get this returned valued of the contract offchain (and by offchain in this case, I mean inside your test environment) is by emitting an Event. So, I made the following change to your code.
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "./Domain.sol";
import "hardhat/console.sol";
contract DomainFactory {
Domain[] private _domains;
event CreatedDomain(address domainAddress);
function createDomain(string memory _publicKey) public returns (address) {
Domain domain = new Domain(_publicKey);
_domains.push(domain);
emit CreatedDomain(address(domain));
return address(domain);
}
function allDomains(uint256 limit, uint256 offset)
public
view
returns (Domain[] memory coll)
{
return coll;
}
}
I just simply emit a CreatedDomain() event containing the deployed Domain address.
One more thing: if I remember correctly, you can only retrieve values direct from function returns offchain, if your function is of type view. Otherwise, you will need to emit the event and then find it later.
In order to test the Domain deployed by the DomainFactory take a look at this test script:
import { expect } from "chai";
import { ethers } from "hardhat";
import { Domain, DomainFactory } from "../typechain";
describe("Domain", function () {
let domainFactory: DomainFactory;
let domain: Domain;
let domainAddress: string;
it("Should deploy a DomainFactory ", async () => {
const DomainFactory = await ethers.getContractFactory("DomainFactory");
domainFactory = await DomainFactory.deploy();
await domainFactory.deployed();
});
it("deploy a Domain using DomainFactory ", async () => {
const tx = await domainFactory.createDomain("public string here");
const rc = await tx.wait();
const event = rc.events?.find((event) => event.event === "CreatedDomain");
const args = event?.args;
if (args) domainAddress = args[0];
});
it("attach an abi interface to the deployed domain", async () => {
const Domain = await ethers.getContractFactory("Domain");
domain = await Domain.attach(domainAddress);
});
it("get data from Domain deployed by DomainFactory ", async () => {
const res = await domain.getChildren();
console.log(res);
});
});
It deploys a DomainFactory then uses the createDomain() method, fetches the deployed address from the functino events, then use it to attach the ABI to the deployed Domain.
Full code here:
https://github.com/pedrohba1/stackoverflow/tree/main/Domain
Anything else related to running it I will be adding in the comments.

Calling the set() function (with web3js) of a solidity contract is creating a new contract address. Why?

I have a simple solidity contract with a set() function. When I call the contract's set() function, the resulting transaction is at a newly created contract address instead of the contract address of where the solidity code lives.
If I use the UI in Remix, the new transactions (with the updated string values) are associated with the original contract. When I attempt the same thing with web3js, brand new contracts are being created.
I am wanting all new get() calls with web3js to be associated with the original contract.
Solidity Code
pragma solidity ^0.4.0;
contract HashRecord {
string public hashValue;
function setHashValue(string newHashValue) public {
hashValue = newHashValue;
}
function getHashValue() public view returns (string) {
return hashValue;
}
}
web3js Code
var Tx = require('ethereumjs-tx')
const Web3 = require('web3')
const web3 = new Web3('https://ropsten.infura.io/v3/d55489f8ea264a1484c293b05ed7eb85')
const abi = [ABI_CODE]
const contractAddress = '0x6c716feb775d5e7b34856edf75048a13fe0c16b0'
const myAccount = '0x59f568176e21EF86017EfED3660625F4397A2ecE'
const privateKey1 = new Buffer('PRIVATE_KEY', 'hex')
hashValue = 'newly updated value'
const contract = new web3.eth.Contract(abi, contractAddress,{
from: myAccount,
web3.eth.getTransactionCount(myAccount, (err, txCount) => {
//Smart contract data
const data = contract.methods.setHashValue(hashValue).encodeABI()
// Build the transaction
const txObject = {
nonce: web3.utils.toHex(txCount),
gasLimit: web3.utils.toHex(1000000),
gasPrice: '5000',
data: data,
from: myAccount,
}
// Sign the transaction
const tx = new Tx(txObject)
tx.sign(privateKey1)
const serializedTx = tx.serialize()
// Broadcast the transaction
web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex')).
on('receipt', console.log)
})
My guess is this has to do with const contract = new web3.eth.Contractcreating a new contract. I can not figure out another way to do it though.
Again, I would like new values stored under the variable hashValue to be associated with the original contract address of const contractAddress = '0x6c716feb775d5e7b34856edf75048a13fe0c16b0'
Thanks!!!
Adding to: contractAddress,
in the following code block
const txObject = {
nonce: web3.utils.toHex(txCount),
// value: web3.utils.toHex(web3.utils.toWei('0.1', 'ether')),
gasLimit: web3.utils.toHex(1000000),
gasPrice: '5000',
// gasPrice: '0x' + estimatedGas,
data: data,
from: myAccount,
to: contractAddress,
solves the problem.
Thank you #smarx!

Call contract methods with web3 from newly created account

I need to call methods from my contract in Ethereum without using MetaMask. I use Infura API and try to call my methods from account, recently created with web3.eth.create() method. This method returns object like this:
{
address: "0xb8CE9ab6943e0eCED004cG5834Hfn7d",
privateKey: "0x348ce564d427a3311b6536bbcff9390d69395b06ed6",
signTransaction: function(tx){...},
sign: function(data){...},
encrypt: function(password){...}
}
I also using infura provider:
const web3 = new Web3(new Web3.providers.HttpProvider(
"https://rinkeby.infura.io/5555666777888"
))
So, when I try to write smth like that:
contract.methods.contribute().send({
from: '0xb8CE9ab6943e0eCED004cG5834Hfn7d', // here I paste recently created address
value: web3.utils.toWei("0.5", "ether")
});
I have this error:
Error: No "from" address specified in neither the given options, nor
the default options.
How it could be no from address if I write it in from option??
P.S. With Metamask my application works fine. But when I logout from MetaMask and try to create new account and use it, I have that issue.
In fact, we can't just send transactions from newly created address. We must sign this transaction with our private key. For example, we can use ethereumjs-tx module for NodeJS.
const Web3 = require('web3')
const Tx = require('ethereumjs-tx')
let web3 = new Web3(
new Web3.providers.HttpProvider(
"https://ropsten.infura.io/---your api key-----"
)
)
const account = '0x46fC1600b1869b3b4F9097185...'; //Your account address
const privateKey = Buffer.from('6e4702be2aa6b2c96ca22df40a004c2c944...', 'hex');
const contractAddress = '0x2b622616e3f338266a4becb32...'; // Deployed manually
const abi = [Your ABI from contract]
const contract = new web3.eth.Contract(abi, contractAddress, {
from: account,
gasLimit: 3000000,
});
const contractFunction = contract.methods.createCampaign(0.1); // Here you can call your contract functions
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: 100000,
gasLimit: 3000000,
to: contractAddress,
data: functionAbi,
from: account,
nonce: '0x' + nonce
};
const tx = new Tx(txParams);
tx.sign(privateKey); // Transaction Signing here
const serializedTx = tx.serialize();
web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex')).on('receipt', receipt => {
console.log(receipt);
})
});
});
Transaction time is about 20-30 seconds so you should wait quite a bit.

Send signed transactions to Ropsten or Truffle develop network with Trezor (Hardware Wallet)

Im trying to integrate web3js with Trezor in a truffle dev network or using ropsten test network.
The idea is to sign the transactions using the hardware wallet and then send a raw transaction using web3js
Im getting that we dont have balance to make the transaction, probably because web3js isnt taking one of the 10 truffle accounts and is using the trezor address that isnt in my local network..
On ropsten i have some ethers and i get "invalid address"
Is there a way to send a signed transactions (with trezor) using web3js into a truffle develop network? i mean, is there a way to include the trezor address into the truffle network?
The situation in truffle is explained more in details here, but the question could be generalized to "is there a way to include hardware wallets into truffle development network?" : https://github.com/trufflesuite/truffle/issues/973
Using ropsten I have managed to send a transaction and receive a transaction hash in the callback, but if we query for that transaction we get that the transaction doesnt exists.. so.. how is that possible?
I tried deploying a contract into Ropsten too and now im getting "Invalid address" when invoking a smart contract function. Maybe the signing function is wrong? anyone could integrate Trezor transaction signining with web3js?
Do you guys see anything wrong in the signing and sending process that we have followed? Maybe is there something wrong on the R, V and S parameters handling
..
Another important thing is that i am using https://github.com/ethereumjs/ethereumjs-tx for creating the raw transactions
Issues published in web3js, truffle and trezzor connect with more information:
https://github.com/trufflesuite/truffle/issues/973
https://github.com/ethereum/web3.js/issues/1669
https://github.com/trezor/connect/issues/130
kind regards
trezorLogin = async()=> {
let trezor= await this.getTrezor();
// site icon, optional. at least 48x48px
let hosticon = 'https://doc.satoshilabs.com/trezor-apps/_images/copay_logo.png';
// server-side generated and randomized challenges
let challenge_hidden = '';
let challenge_visual = '';
//use anonimous functions on callback otherwise returns cross origin errors
trezor.requestLogin(hosticon, challenge_hidden, challenge_visual, function (result){
if (result.success) {
console.log('Public key:', result.public_key); // pubkey in hex
console.log('Signature:', result.signature); // signature in hex
console.log('Version 2:', result.version === 2); // version field
console.log(result);
}else {
console.error('Error:', result.error);
}
});}
trezorSignTx= async(transaction)=> {
let trezor= await this.getTrezor();
// spend one change output
let address_n = "m/44'/60'/0'/0/0"
// let address_n = [44 | 0x80000000,
// 60 | 0x80000000,
// 0 | 0x80000000 ,
// 0 ]; // same, in raw form
let nonce = transaction.nonce.substring(2); // note - it is hex, not number!!!
let gas_price = transaction.gasPrice.substring(2);
let gas_limit = transaction.gasLimit.substring(2);
let to = transaction.to.substring(2);
// let value = '01'; // in hexadecimal, in wei - this is 1 wei
let value = transaction.value.substring(2); // in hexadecimal, in wei - this is about 18 ETC
let data = transaction.data.substring(2); // some contract data
// let data = null // for no data
let chain_id = 5777; // 1 for ETH, 61 for ETC
return new Promise (function (resolve,reject) {
trezor.ethereumSignTx(
address_n,
nonce,
gas_price,
gas_limit,
to,
value,
data,
chain_id,
function (response) {
if (response.success) {
console.log('Signature V (recovery parameter):', response.v); // number
console.log('Signature R component:', response.r); // bytes
console.log('Signature S component:', response.s); // bytes
resolve(response);
} else {
console.error('Error:', response.error); // error message
resolve(null);
}
});
})
}
getTrezorAddress = async() => {
let trezor= await this.getTrezor();
// spend one change output
let address_n = "m/44'/60'/0'/0/0";
trezor.ethereumGetAddress(address_n, function (result) {
if (result.success) { // success
console.log('Address: ', result.address);
} else {
console.error('Error:', result.error); // error message
}
});
}
getTrezor = async() => {
let trezorC;
await getTrezorConnect
.then(trezorConnect => {
trezorC= trezorConnect;
})
.catch((error) => {
console.log(error)
})
return trezorC;
}
sendTransaction= async(address, amount, id)=>{
let tokenInstance = this.props.smartContractInstance;
let getData = tokenInstance.mint.getData(address, amount);
let tx = {
nonce: '0x00',
gasPrice: '0x09184e72a000',
gasLimit: '0x2710',
to: CONTRACT_ADDRESS,
value: '0x00',
from:CONTRACT_OWNER_ADDRESS,
data: getData
};
let response = await this.trezorSignTx(tx);
let web3;
let _this = this;
if (response!=null){
getWeb3
.then(results => {
web3= results.web3;
let v = response.v.toString();
if (v.length % 2 != 0){
v="0"+v;
}
tx.r=Buffer.from(response.r,'hex');
tx.v=Buffer.from(v,'hex');
tx.s=Buffer.from(response.s,'hex');
let ethtx = new ethereumjs(tx);
console.dir(ethtx.getSenderAddress().toString('hex'), );
const serializedTx = ethtx.serialize();
const rawTx = '0x' + serializedTx.toString('hex');
console.log(rawTx);
//finally pass this data parameter to send Transaction
web3.eth.sendRawTransaction(rawTx, function (error, result) {
if(!error){
_this.props.addTokens(id)
.then(()=>{
_this.setState({modalOpen: true});
_this.props.getAllTransactions();
}
);
}else{
alert(error)
}
});
})
.catch((error) => {
console.log(error)
})
}else{
alert("There was an error signing with trezor hardware wallet")
}
}
The getTrezorConnect function is just get window.trezorConnect asynchronously because the object is injected as script
<script src="https://connect.trezor.io/4/connect.js"></script>
let getTrezorConnect = new Promise(function(resolve, reject) {
// Wait for loading completion
window.addEventListener('load', function() {
let trezorConnect = window.TrezorConnect
return resolve(trezorConnect)
})});
export default getTrezorConnect
Well, after a lot of trying we have managed to send a raw transaction signed with Trezor to Ropsten, Truffle (see the edit on the bottom of the answer) and also to a local private Geth network, so, the code is ok and there is no problem with Trezor integration on those environments
https://ropsten.etherscan.io/address/0x89e2c46b22881f747797cf67310aad1a831d50b7
This are the things that i had changed in order to make it possible to send signed transactions to the Ropsten testnet.
This assumes that you have your contract deployed into Ropsten and you have the contract address.
1) Get the address of your Trezor account
getTrezorAddress = async() => {
let trezor= await this.getTrezor();
// spend one change output
let address_n = "m/44'/1'/0'/0/0";
trezor.ethereumGetAddress(address_n, function (result) {
if (result.success) { // success
console.log('Address: ', result.address);
} else {
console.error('Error:', result.error); // error message
}
});
}
2) Put the trezor address into the from field of your raw transaction, get the nonce of the transaction by getting the transaction count for that address. Important: use the "pending" optional parameter on getTransactionCount to get all the transactions of the account, otherwise you will be overriting pending transactions.
getNonce = async(address) => {
let web3 = await this.getWeb3();
return new Promise (function (resolve,reject) {
web3.eth.getTransactionCount(address, "pending", function (error,result){
console.log("Nonce "+result);
resolve(result);
});
});
}
let count = null;
await this.getNonce("0xedff546ac229317df81ef9e6cb3b67c0e6425fa7").then(result => {
if(result.length % 2 !==0){
result = "0"+result;
}
count = "0x"+result;
});
let tx = {
nonce: count ,
gasPrice: web3.toHex(gasPriceGwei*1e9),
gasLimit: web3.toHex(gasLimit),
to: CONTRACT_ADDRESS,
value: '0x00',
data: getData,
chainId:chainId,
from:"yourTrezzorAddress"
};
3) The r, s, v parameters were incorrect, the right way to handle them is take that values for the trezor response and just convert it to hexa:
// response is the Trezor sign response
tx.v= response.v;
tx.r="0x"+response.r;
tx.s="0x"+response.s;
let ethtx = new ethereumjs(tx);.
const serializedTx = ethtx.serialize();
const rawTx = '0x' + serializedTx.toString('hex');
//finally pass this data parameter to send Transaction
web3.eth.sendRawTransaction(rawTx, someCallbackFunction);
Important: the mining time in ropsten will be between 15 and 30 secs so if in your someCallbackFunction you check for the transaction receipt, using the hash, you will get null as result, because the transaction is in a pending state.
4) To test it at ropsten we use Infura, so we change the web3 provider:
import Web3 from 'web3'
import HDWalletProvider from "truffle-hdwallet-provider";
let getWeb3 = new Promise(function(resolve, reject) {
// Wait for loading completion to avoid race conditions with web3 injection timing.
window.addEventListener('load', function() {
let results
let web3 = window.web3
// Checking if Web3 has been injected by the browser (Mist/MetaMask)
if (typeof web3 !== 'undefined') {
// Use Mist/MetaMask's provider.
web3 = new Web3(web3.currentProvider)
results = {
web3: web3
}
console.log('Injected web3 detected.');
return resolve(results)
} else {
// Fallback to localhost if no web3 injection. We've configured this to
// use the development console's port by default.
// let provider = new Web3.providers.HttpProvider("https://ropsten.infura.io/your_infura_api_key")
let mnemonic = "infura mnemonic"
let provider = new HDWalletProvider(mnemonic, "https://ropsten.infura.io/your_infura_api_key")
web3 = new Web3(provider)
results = {
web3: web3
}
console.log('No web3 instance injected, using Local web3.');
return resolve(results)
}
})
})
export default getWeb3
EDIT:
This also works on Truffle! check the last comments of this issue https://github.com/trufflesuite/truffle/issues/973
We developed a small library to use Truffle with Trezor hardware wallets: https://github.com/rarible/trezor-provider
It can be used like this:
const { createProvider } = require('#rarible/trezor-provider')
module.exports = {
networks: {
ropsten: {
provider: function() {
//websocket and http urls are supported
return createProvider({ url: "{infura or other}", path: "m/44'/60'/0'/0/0", chainId: 3 })
},
network_id: 3
}
}
};

Sending signed transactions to Ropsten

It's easy to use web3js to call functions that don't require signing (e.g. 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:
Is this generally the right way to do what I am trying to do?
If 1 is true, how do I get the nonce parameter for a deployed contract?
References:
http://web3js.readthedocs.io/en/1.0/
https://github.com/ethereumjs/ethereumjs-tx
https://ethereum.stackexchange.com/questions/21402/web3-eth-call-how-can-i-set-data-param
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)
})
})