Subscribing to a Solidity event from frontend - ethereum

I am trying to subscribe to a PurchaseMade event defined in Solidty from the frontend. I am not getting the expected results and need help with what I'm doing wrong.
Environment:
ganache-cli, Truffle
web3.js, React.js
Initialing contract Instance:
export const getContractInstance = () => {
let web3Provider
if (typeof window.web3 !== 'undefined') {
// if metamask is on, web3 is injected...
web3Provider = web3.currentProvider
} else {
// otherwise, use ganache-cli...
web3Provider = new Web3.providers.HttpProvider('http://localhost:8545')
}
web3 = new Web3(web3Provider)
return new web3.eth.Contract(CryptoKpopAbi, CONTRACT_ADDRESS)
}
Subscribing to PurchaseMade event
onBuy = (obj) => {
web3.eth.subscribe("PurchaseMade", {}, () => {
debugger
});
this.ContractInstance.methods.buy(1).send({
from: this.state.currentUserAddress,
gas: GAS_LIMIT,
value: web3.utils.toWei(price.toString(), "ether"),
}).then((receipt) => {
console.log(receipt)
}).catch((err) => {
console.log(err.message)
})
}
I get this warning when I call web3.eth.subscribe:
Subscription "PurchaseMade" doesn't exist. Subscribing anyway.
I get this error on tx receipt (after send()` succeeds
Uncaught TypeError: Cannot read property 'subscriptionName' of undefined
I used this official doc to setup the subscription
http://web3js.readthedocs.io/en/1.0/web3-eth-subscribe.html
Thank you in advance!
UPDATE:
Event declaration in contract
event PurchaseMade(uint objId, uint oldPrice, uint newPrice, string objName, address prevOwner, address newOwner);
Event call in contract
function buy(uint _tokenId) payable public {
address prevOwner = ownerOf(_tokenId);
uint currentPrice = tokenIdToPrice[_tokenId];
...
PurchaseMade(_tokenId, currentPrice, newPrice,
tokens[_tokenId].name, prevOwner, msg.sender);
}

You're attempting to subscribe to the event itself. The API lets you subscribe to an event type and add filters. The valid event types are:
pendingTransactions: Receive a subset of new transactions sent to the blockchain (Primarily used for miners who want to be selective of the transactions they process)
newBlockHeaders: Receive notification when a new block has been added to the blockchain.
syncing: Receive notification when node syncing starts/stops
logs: Receive notification on log updates on the blockchain. These are the events you're interested in.
Look at the API documentation for examples on how to use subscribe("logs").
The subscribe API is usually used to listen to events occurring across the blockchain. An easier approach for listening to events for a specific contract is to use events for the deployed contract (documentation). It's not much different than using subscribe above, but it already has the contract address and topic filters.
this.ContractInstance.events.PurchaseMade({}, (error, data) => {
if (error)
console.log("Error: " + error);
else
console.log("Log data: " + data);
});
There's one important note, though. With web3 1.0, listening to events is not supported using HttpProvider. You have to use Websockets or IPC.
EDIT - I forgot to mention you can also get the events from the transaction receipt:
contractInstance.events.eventName.returnValues;

Related

Ethers.js - Swap Event Listener - No Data Returned

I am trying to set up an event listener for "Swap" events on BSC. I have followed the example on the Ethers docs, but I am not recieving any information back even though there are swap events being emitted (confirmed on BSCScan).
Can you tell me if I am doing something wrong?
const { ethers } = require("ethers");
const provider = new ethers.providers.JsonRpcProvider('https://bsc-dataseed1.binance.org')
const address = '0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE' // BNB/USDT pair
const pairABI = [
"function name() view returns (string)",
"event Swap(address indexed sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, address indexed to)"
];
const contract = new ethers.Contract(address, pairABI, provider)
const main = async () => {
const name = await contract.name()
console.log(`\nListening for swap events for ${name}\n`)
// Receive an event when ANY swap occurs
contract.on("Swap", (sender, amount0In, amount1In, amount0Out, amount1Out, to, event) => {
console.log(sender);
});
}
main()
I've followed the example on the Ethers.js guidance docs and also looked at a bunch of other examples and I seem to be doing the right thing, but getting nowhere.
You need to use a web socket provider, like so:
const provider = new ethers.providers.WebSocketProvider("wss://...");
Check official doc - https://docs.ethers.org/v5/api/providers/other/#WebSocketProvider

Ethers.js event listeners - Expected behavior for many getLogs/chainId/blocknumber requests?

I'm building a minting site that requires me to check the number of NFTs minted and display that number in real time to the user.
At first I was just making a request every few seconds to retrieve the number, but then I figured I could use an event listener to cut down on the requests, as people would only be minting in short bursts.
However, after using the event listener, the volume of requests has gone way up. Looks like it is constantly calling blockNumber, chainId, and getLogs. Is this just how an event listener works under the hood? Or do am I doing something wrong here?
This is a next js API route and here is the code:
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import { ethers } from 'ethers'
import { contractAddress } from '../../helpers'
import type { NextApiRequest, NextApiResponse } from 'next'
import abi from '../../data/abi.json'
const NEXT_PUBLIC_ALCHEMY_KEY_GOERLI =
process.env.NEXT_PUBLIC_ALCHEMY_KEY_GOERLI
let count = 0
let lastUpdate = 0
const provider = new ethers.providers.JsonRpcProvider(
NEXT_PUBLIC_ALCHEMY_KEY_GOERLI,
'goerli'
)
const getNumberMinted = async () => {
console.log('RUNNING NUMBER MINTED - MAKING REQUEST', Date.now())
const provider = new ethers.providers.JsonRpcProvider(
NEXT_PUBLIC_ALCHEMY_KEY_GOERLI,
'goerli'
)
const contract = new ethers.Contract(contractAddress, abi.abi, provider)
const numberMinted = await contract.functions.totalSupply()
count = Number(numberMinted)
lastUpdate = Date.now()
}
const contract = new ethers.Contract(contractAddress, abi.abi, provider)
contract.on('Transfer', (to, amount, from) => {
console.log('running event listener')
if (lastUpdate < Date.now() - 5000) {
getNumberMinted()
}
})
export default function handler(req: NextApiRequest, res: NextApiResponse) {
try {
res.setHeader('Content-Type', 'application/json')
res.status(200).json({ count })
} catch (err) {
res
.status(500)
.json({ error: 'There was an error from the server, please try again' })
}
}
If you use the AlchemyProvider or directly the StaticJsonRpcProvider (which ApchemyProvider inherits) you will eliminate the chainId calls; those are used to ensure the network hasn’t changed, but if you using a third-party service, like Alchemy or INFURA, this isn’t a concern which is why the StaticJsonRpcProvider exists. :)
Then every pollingInterval, a getBlockNumber is made (because this is a relatively cheap call) to detect when a new block occurs; when a new block occurs, it uses the getLogs method to find any logs that occurred during that block. This minimizes the number of expensive getLogs method.
You can increase or decrease the pollingInterval to trade-off latency for server resource cost.
And that’s how events work. :)
Does that make sense?

Error: invalid BigNumber value (argument="value", value={"value":"25000000000000000"}, code=INVALID_ARGUMENT, version=bignumber/5.5.0)

I have tried changing the values from 0.025 ether to 1 ether then also its showing the same error.
Also, I have tried with the rational number like 1/8 still not working.
LOOKED into some answers but they didn't resolve the error.
I have the same code in other project and it's working over there.
Error Which I received
Uncaught (in promise) Error: invalid BigNumber value (argument="value", value={"value":"25000000000000000"}, code=INVALID_ARGUMENT, version=bignumber/5.5.0)
Could not get the stack frames of error: TypeError: Cannot read properties of null (reading 'length')
Image of the Error
Here is my Code for the Listing Price
uint256 listingPrice = 0.025 ether ; // Here ether is denoting the MATIC
function getListingPrice() public view returns (uint256) {
return listingPrice;
}
Here is the Code for fetching the value in UI
async function putItem(url) {
const web3Modal = new Web3Modal();
const connection = await web3Modal.connect();
const provider = new ethers.providers.Web3Provider(connection);
const signer = provider.getSigner();
const { royalty } = formInput;
//NFT Contract
let contract = new ethers.Contract(nftAddress, NFT.abi, signer);
//minting the certificate
let transaction = await contract.createToken(url);
//waiting for the minting transaction to finish
let tx = await transaction.wait();
let event = tx.events[0];
let value = event.args[2];
let tokenId = value.toNumber(); //Token Id Of the NFT
console.log(tokenId)
//NFT Market Contract
contract = new ethers.Contract(nftMarketAddress, NFTMarket.abi, signer);
//fetching listing price from the contract
let listingPrice = await contract.getListingPrice();
listingPrice = listingPrice.toString();
//listing the certificate.
transaction = await contract.createMarketItem(
nftAddress,
tokenId,
{ value: (listingPrice) },
royalty,
index
);
//waiting for the transaction to complete
await transaction.wait();
console.log("completed")
//navigate back to home page
}
If any more detail required, please comment.
It looks like you're trying to send an object as the parameter { value: (listingPrice) }
This should probably be written as either an array of parameters or just the listingPrice
//listing the certificate.
transaction = await contract.createMarketItem(
nftAddress,
tokenId,
listingPrice,
royalty,
index
);
Source: https://docs.ethers.io/v5/api/contract/contract/#contract-functionsSend
For my case I needed to add .toString() to the BigNumber before passing it to the contract.
async changePayoutAmount_ether(amount_ether) {
let amount_wei = new BigNumber(amount_ether).shiftedBy(18).toString()
await this.state.pcrContract.methods.setPayoutAmount(amount_wei).send({from: this.state.account}).then(console.log)
}
Also for anyone troubleshooting, note that there are at least two BigNumber libraries: I believe this error comes from this one but be careful if you're reading docs from the ethers.js one because the syntax for the constructors is different.
I got this error as well. In my case, I forgot to update the ABI.
You can use the following module:
import converter form "ethereum-uint-converter"
And if you want to know more detail, click here.
I think this is the issue:
transaction = await contract.createMarketItem(
nftAddress,
tokenId,
{ value: (listingPrice) },
royalty,
index
);
{ value: (listingPrice) }, is supposed to be object that represents the amount of money you are sending along side the transaction and it should be the last parameter in the function. Because looks like you are creating an NFT market item and you have to pay the price of listing.
Since you are creating an nft, looks like you have a const { royalty } = formInput. I believe you wanted to send the nft price instead of { value: (listingPrice) }. so your transaction should be like this
transaction = await contract.createMarketItem(
nftAddress,
tokenId,
// I assume you want to send the nft price here from form
priceFromForm,
royalty,
index,
// this should be the last parameter
{ value: (listingPrice) }
);

How to send some custom tokens with MetaMask API?

It seems the example code on the MetaMask documentation page, sends ETH only. How should I customize the sample code to send some custom tokens?
const transactionParameters = {
nonce: '0x00', // ignored by MetaMask
gasPrice: '0x09184e72a000', // customizable by user during MetaMask confirmation.
gas: '0x2710', // customizable by user during MetaMask confirmation.
to: '0x0000000000000000000000000000000000000000', // Required except during contract publications.
from: ethereum.selectedAddress, // must match user's active address.
value: '0x00', // Only required to send ether to the recipient from the initiating external account.
data:
'0x7f7465737432000000000000000000000000000000000000000000000000000000600057', // Optional, but used for defining smart contract creation and interaction.
chainId: '0x3', // Used to prevent transaction reuse across blockchains. Auto-filled by MetaMask.
};
// txHash is a hex string
// As with any RPC call, it may throw an error
const txHash = await ethereum.request({
method: 'eth_sendTransaction',
params: [transactionParameters],
});
A transaction sending an ERC-20 token needs to have the token contract as the recipient (the to field), and the data field containing encoded instructions to execute its transfer() function along with address of the token receiver and amount.
const transactionParameters = {
from: accounts[0],
to: tokenContractAddress,
data: getDataFieldValue(tokenRecipientAddress, tokenAmount),
};
await ethereum.request({
method: 'eth_sendTransaction',
params: [transactionParameters],
});
You can use for example the web3js library (docs) to encode the data field value. The transfer() function is standardized - so assuming the token contract follows the standard, it's going to be the same for any token contract.
function getDataFieldValue(tokenRecipientAddress, tokenAmount) {
const web3 = new Web3();
const TRANSFER_FUNCTION_ABI = {"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"};
return web3.eth.abi.encodeFunctionCall(TRANSFER_FUNCTION_ABI, [
tokenRecipientAddress,
tokenAmount
]);
}
For a cleaner version of Petr's solution using the encodeABI function.
await window.ethereum
.request({
method: "eth_sendTransaction",
params: [
{
from: accounts[0],
to: TOKEN_CONTRACT_ADDRESS,
data: tokenContract.methods
.transfer(receiverAddress, amount)
.encodeABI(),
},
],
})
.then((result) => console.log(result))
.catch((error) => console.error(error));

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);
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:
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.
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.
Well first check if the wallet that you are using to call the functions is the owner of the tokens and not the dapp, if the dapp is the owner i would recommend you that the address you are using to call the contract have an owner permission and add the dapp contract a function to send the token to some address, if the address that you are using is the owner of the tokens just call the ´TOKEN_CONTRACT.methods.transfer(post_addr,transfer_amt)´
now, just as an explanation, the reason because that endpoint is not sending the tokens is this, in that operation are 4 address used, the user address, the contract address, the token address and the address you are using to send the transactions from the backend, when you call TOKEN_Contract.methods.approve(DAppAddr, regular_post_transfer_amt) you are approving the contract address to move the tokens that owns the address you use in your backend not viceversa, for that the contract would have made that call and pass as a parameter the backend address, so when you call TOKEN_Contract.methods.transferFrom(DAppAddr, post_addr, transfer_amt) you are trying to move the tokens of the contract approved to the backend address and send it to the user address, but the amount of this approved tokens is 0, because the thing i explained before