Ethers.js - Swap Event Listener - No Data Returned - ethereum

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

Related

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) }
);

Web3JS contract call returning a different value than direct contract call with remix

I have a contract deployed onchain:
function getNonces(bytes32 _hashWalletSig, uint256[] calldata _dataTypes)
public
view
virtual
returns (uint8[] memory, bytes32[] memory)
{
...
}
If I point the remix editor to this contract and call the getNonce() function directly with the parameters:
0xa58d6abf78c9492df62daf0e251c6af49d5101eaadf2c4786e277a8da97f9f73 and [0]
I get my expected result.
However, I have a frontend application using Web3Js that attempts to call the getNonce function but I'm getting a completely different value (i.e. 0x00000000) which indicates that nothing is found:
export const getBlockchainStatus = async (walletSignature: string): Promise<boolean> => {
const MainContract = new web3.eth.Contract(
Ky0xMainAbi as AbiItem[],
CONTRACT_ADDRESS,
);
const hashwalletSig = ethers.utils.keccak256(walletSignature);
console.log('hashwalletSig:', hashwalletSig);
const response = await MainContract.methods.getNonces(hashwalletSig, ['0']).call();
console.log('response', response[1][0]);
return response[1][0] !== '0x0000000000000000000000000000000000000000000000000000000000000000';
};
The hashWalletSig matches the original value that I passed into the remix editor.
Can someone tell me what I'm doing wrong in Web3JS?

How to sign an ethereum transaction with metamask

I am currently sending transactions with this code:
const provider = ethers.getDefaultProvider("ropsten", {
infura:
"https://ropsten.infura.io/v3/ee11be9f1d1c43199618db4a7b22aa79",
});
const signer = new ethers.Wallet(PRIVATE_KEY);
const account = signer.connect(provider);
const uniswap = new ethers.Contract(
ropstenUniswapContract,
[
"function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) external payable returns (uint[] memory amounts)",
],
account
);
const gasCost = ethers.BigNumber.from((+gasPrice/10) * Math.pow(10, 9));
console.log('Computed gas cost ->', gasCost);
const tx = await uniswap.swapExactETHForTokens(
amountOutMin,
path,
to,
deadline,
{ value, gasPrice: gasCost }
);
// Transaction Hash and Block
setTransactionHash(tx.hash);
const receipt = await tx.wait();
console.log(receipt);
My question is:
How can I make MetaMask sign the transaction on my behalf instead of supplying my private key?
Transaction signing is abstracted away with web3.js or Ethers.js. You can directly connect your Ethers.js to MetaMask provider (windows.ethereum) in in-page JavaScript code.
An example here.
Your (browser-facing) app can communicate with the MetaMask browser extension using the Ethereum Provider API.
Speficically the eth_sendTransaction method passing it the data field (specifying which function in your contract you want to execute, as well as the passed argument values), along with other params such as the sender address.
If you're new to MetaMask, just a quick note: To get the user's list of addresses, you need to call the eth_requestAccounts method.

Subscribing to a Solidity event from frontend

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;