I am getting trouble calling the contract methods in front end - ethereum

I want to call my contract from the browser itself so I have used Metamask for that and it works seamlessly but the thing I don't want to use Metamask because of Metamask cannot be used in phone
I have used metamask and it works
I have used trufflehdwallet in backend and it works
I have used testrpc network and it also works
if (window.ethereum) {
window.web3 = new Web3(ethereum);
try {
// Request account access if needed
await ethereum.enable();
web3.eth.getAccounts(function (err, accounts) {
if (!err) {
get_contract();
}
})
} catch (error) {
// User denied account access...
}
}
// Legacy dapp browsers...
else if (window.web3) {
window.web3 = new Web3(web3.currentProvider);
web3.eth.getAccounts(function (err, accounts) {
if (!err) {
get_contract();
}
})
}
// Non-dapp browsers...
else {
console.log('Non-Ethereum browser detected. You should consider installing MetaMask !');
}
I want something like this
web3 = new Web3(new Web3.providers.HttpProvider("https://ropsten.infura.io/v3/24b7104373aa4038a2d0b64d9d60bd85"));

I think your question is different than the title, actually.
From your question, I understood that you want to use your web dapp on your smartphone.
Well, if that is the question, I have a few options for you. First of all, if you are using the mainnet, you have a few mobile apps, such as GnosisSafe or TrustWallet. If you are using testnets, there's a new wallet named Portis, that allows you to connect to public testnets. Honestly, I don't recommend you to test local networks, as it is a web dapp and you can test on your laptop with metamask.
So, my final hint would be web3connect. This allows the user to choose his wallet.

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?

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

I need a simple way to sign data via web3 and metamask

I need a very simple way to sign data with Metamask and Web3. I am very familiar with using eth account sign with Web3, but I want to incorporate Metamask into it. I have read the Metamask docs on signing data, but the examples they gave are outdated.
The one thing I have done: Successfully enabled Ethereum and got access to the user's address who connected.
Any suggestions on a very very simple way to sign data? I'm really just testing things out Metamask and want to get started.
const getSignedData = async () => {
const messageToSign = "any message you create or fetch";
const accounts = (await ethereum?.request({
method: "eth_requestAccounts",
})) as string[];
// account will be the signer of this message
const account = accounts[0];
// password is the third param as uuid
const signedData = await ethereum?.request({
method: "personal_sign",
params: [
JSON.stringify(messageToSign.data),
account,
messageToSign.data.id,
],
});
return { signedData, account };
};

Fastest way to port a Web App to Mobile App

Is there any way to port a complete Web App (which is already responsive and fully compatible with small screens, already has touch UI controls, etc.) to Android/iOS?
My Web App is barebone HTML/JS/CSS, so is super vanilla (I don't even use jQuery).
I thought I could just smash my web app into an empty Ionic-Cordova project and be good with it, but I was wondering is there is a faster/better way to do this?
Maybe a tool or service i don't know about that takes as input a folder and pops out an android/IOS executable?
You can make a PWA (Progressive Web App).
Progressive Web Apps (PWAs) are modern, high quality applications built using web technology. PWAs offer similar capabilities to iOS/Android/desktop apps, they are reliable even in unstable network conditions, and are installable making it easier for users to find and use them.
Basically you have to add a manifest file in .json to your project root where you'll inform many things about your App like icon, name, main color, display mode (choose standalone if you want it to be like an real app) and etc...
(see it here: https://web.dev/add-manifest/) and link to your project pages:
<link rel="manifest" href="/manifest.json">
After that you have to make it installable (https://web.dev/codelab-make-installable/), to do that you will need a service-worker script in your project, you can get one here (https://glitch.com/edit/#!/make-it-installable?path=service-worker.js%3A1%3A0)
const CACHE_NAME = 'offline';
const OFFLINE_URL = 'offline.html';
self.addEventListener('install', function(event) {
console.log('[ServiceWorker] Install');
event.waitUntil((async () => {
const cache = await caches.open(CACHE_NAME);
// Setting {cache: 'reload'} in the new request will ensure that the response
// isn't fulfilled from the HTTP cache; i.e., it will be from the network.
await cache.add(new Request(OFFLINE_URL, {cache: 'reload'}));
})());
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
console.log('[ServiceWorker] Activate');
event.waitUntil((async () => {
// Enable navigation preload if it's supported.
// See https://developers.google.com/web/updates/2017/02/navigation-preload
if ('navigationPreload' in self.registration) {
await self.registration.navigationPreload.enable();
}
})());
// Tell the active service worker to take control of the page immediately.
self.clients.claim();
});
self.addEventListener('fetch', function(event) {
// console.log('[Service Worker] Fetch', event.request.url);
if (event.request.mode === 'navigate') {
event.respondWith((async () => {
try {
const preloadResponse = await event.preloadResponse;
if (preloadResponse) {
return preloadResponse;
}
const networkResponse = await fetch(event.request);
return networkResponse;
} catch (error) {
console.log('[Service Worker] Fetch failed; returning offline page instead.', error);
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(OFFLINE_URL);
return cachedResponse;
}
})());
}
});
Just add and save it in .js file in your project.
After that make sure you register the service worker using that code in your project:
/* Only register a service worker if it's supported */
// Service Worker
window.addEventListener('load', () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js');
}
});
Now you can make your site installable via some <button> element for example:
window.addEventListener('beforeinstallprompt', (event) => {
// Get the event first
window.deferredPrompt = event;
});
document.querySelector('#buttonInstall').addEventListener('click', () => {
const promptEvent = window.deferredPrompt;
if (! promptEvent) {
return;
}
promptEvent.prompt();
promptEvent.userChoice.then((result) => {
window.deferredPrompt = null;
});
}
});
You can hide the install button when people are in your PWA this way:
if (! window.matchMedia('(display-mode: standalone)').matches) {
// hide your install button
}
Here is some important things:
Your app have to meets certain criteria to be installable, you can
see it here: https://web.dev/install-criteria/
If the install pop-up doesn't appear, it means you made something
wrong, or your manifest is broken or your script.
You can see if your manifest.json is ok in browser developer tools open it (F12), go to Application tab and go to Manifest, this will show all your manifest parameters and it will show if something is wrong too.
I recommend you to read all the links above, there is a lot more
details an explanation about PWAs
simple way to port web app to mobile app is to make a WebView app in android. then give it your web app link address

Login to Chrome extension with a Google user other than the one in use by Chrome

I have a Chrome extension that requests a user to login using the chrome.identity.getAuthToken route. This works fine, but when you login you can only use the users that you have accounts in Chrome for.
The client would like to be able to login with a different Google account; so rather than using the.client#gmail.com, which is the account Chrome is signed in to, they want to be able to login using the.client#company.com, which is also a valid Google account.
It is possible for me to be logged in to Chrome with one account, and Gmail with a second account, and I do not get the option to choose in the extension.
Is this possible?
Instead of authenticating the user using the chrome.identity.getAuthToken , just implement the OAuth part yourself.
You can use libraries to help you, but the last time I tried the most helpful library (the Google API Client) will not work on a Chrome extension.
Check out the Google OpenID Connect documentation for more info. In the end all you have to do is redirect the user to the OAuth URL, use your extension to get Google's answer (the authorization code) and then convert the authorization code to an access token (it's a simple POST call).
Since for a Chrome extension you cannot redirect to a web server, you can use the installed app redirect URI : urn:ietf:wg:oauth:2.0:oob. With this Google will display a page containing the authorization code.
Just use your extension to inject some javascript code in this page to get the authorization code, close the HTML page, perform the POST call to obtain the user's email.
Based on David's answer, I found out that chrome.identity (as well as generic browser.identity) API now provides a chrome.identity.launchWebAuthFlow method which can be used to launch an OAuth workflow. Following is a sample class showing how to use it:
class OAuth {
constructor(clientId) {
this.tokens = [];
this.redirectUrl = chrome.identity.getRedirectURL();
this.clientId = clientId;
this.scopes = [
"https://www.googleapis.com/auth/gmail.modify",
"https://www.googleapis.com/auth/gmail.compose",
"https://www.googleapis.com/auth/gmail.send"
];
this.validationBaseUrl = "https://www.googleapis.com/oauth2/v3/tokeninfo";
}
generateAuthUrl(email) {
const params = {
client_id: this.clientId,
response_type: 'token',
redirect_uri: encodeURIComponent(this.redirectUrl),
scope: encodeURIComponent(this.scopes.join(' ')),
login_hint: email
};
let url = 'https://accounts.google.com/o/oauth2/auth?';
for (const p in params) {
url += `${p}=${params[p]}&`;
}
return url;
}
extractAccessToken(redirectUri) {
let m = redirectUri.match(/[#?](.*)/);
if (!m || m.length < 1)
return null;
let params = new URLSearchParams(m[1].split("#")[0]);
return params.get("access_token");
}
/**
Validate the token contained in redirectURL.
This follows essentially the process here:
https://developers.google.com/identity/protocols/OAuth2UserAgent#tokeninfo-validation
- make a GET request to the validation URL, including the access token
- if the response is 200, and contains an "aud" property, and that property
matches the clientID, then the response is valid
- otherwise it is not valid
Note that the Google page talks about an "audience" property, but in fact
it seems to be "aud".
*/
validate(redirectURL) {
const accessToken = this.extractAccessToken(redirectURL);
if (!accessToken) {
throw "Authorization failure";
}
const validationURL = `${this.validationBaseUrl}?access_token=${accessToken}`;
const validationRequest = new Request(validationURL, {
method: "GET"
});
function checkResponse(response) {
return new Promise((resolve, reject) => {
if (response.status != 200) {
reject("Token validation error");
}
response.json().then((json) => {
if (json.aud && (json.aud === this.clientId)) {
resolve(accessToken);
} else {
reject("Token validation error");
}
});
});
}
return fetch(validationRequest).then(checkResponse.bind(this));
}
/**
Authenticate and authorize using browser.identity.launchWebAuthFlow().
If successful, this resolves with a redirectURL string that contains
an access token.
*/
authorize(email) {
const that = this;
return new Promise((resolve, reject) => {
chrome.identity.launchWebAuthFlow({
interactive: true,
url: that.generateAuthUrl(email)
}, function(responseUrl) {
resolve(responseUrl);
});
});
}
getAccessToken(email) {
if (!this.tokens[email]) {
const token = await this.authorize(email).then(this.validate.bind(this));
this.tokens[email] = token;
}
return this.tokens[email];
}
}
DISCLAIMER: above class is based on open-source sample code from Mozilla Developer Network.
Usage:
const clientId = "YOUR-CLIENT-ID"; // follow link below to see how to get client id
const oauth = new OAuth();
const token = await oauth.getAccessToken("sample#gmail.com");
Of course, you need to handle the expiration of tokens yourself i.e. when you get 401 from Google's API, remove token and try to authorize again.
A complete sample extension using Google's OAuth can be found here.