Add a button for payment via Metamask on the website - javascript

I need to add transaction functionality for a button on my website, how can I do it? After clicking on the button (I am using the Metamask extension for the Firefox browser), the Metamask interface should open, displaying the details of the transaction. I used the code below but saw that message in the browser console: "Uncaught (in promise) ReferenceError: Web3 is not defined". What could be the problem?
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js">
</script>
</head>
<body>
<div>
<button class="pay-button">Pay</button>
<div id="status"></div>
</div>
<script type="text/javascript">
window.addEventListener('load', async() => {
if (window.ethereum) {
window.web3 = new Web3(ethereum);
try {
await ethereum.enable();
initPayButton()
} catch (err) {
$('#status').html('User denied account access', err)
}
} else if (window.web3) {
window.web3 = new Web3(web3.currentProvider)
initPayButton()
} else {
$('#status').html('No Metamask (or other Web3 Provider) installed')
}
})
const initPayButton = () => {
$('.pay-button').click(() => {
// paymentAddress
const paymentAddress = '0x01910833896EEdf036A99b2CC34df6Da01BB15E3'
const amountEth = 1
web3.eth.sendTransaction({
to: paymentAddress,
value: web3.toWei(amountEth, 'ether')
}, (err, transactionId) => {
if (err) {
console.log('Payment failed', err)
$('#status').html('Payment failed')
} else {
console.log('Payment successful', transactionId)
$('#status').html('Payment successful')
}
})
})
}
</script>
</body>
</html>

I am building a service that solves this. Usage is as simple as opening a popup:
const to = '0x9ebf6f16c0dad9f92eaaca8dbd40944e614338ae'
const value = 0.01 // ether
window.open(`https://pay.buildship.dev/to/${to}?value=${value}`,'payment','width=500, height=800');
If you encounter any issues or interested to ask some questions, you can contact me https://t.me/buildship

You have to import web3.js library adding this line:
<script src="https://cdn.jsdelivr.net/npm/web3#latest/dist/web3.min.js"></script>
Or you can install it by following these instructions: https://github.com/ChainSafe/web3.js

I found that in January Metamask updated their API and that's why the code isn't working.
Here you can find the migraition guide:
https://docs.metamask.io/guide/provider-migration.html

Related

How to check if Metamask is connected after page refreshing

My dApp have to connect to MetaMask. There are two rude solutions in the docs: make user to click connect btn every time manually or just pop up connection confirmation after page load. I want to implement the only convenient solution: first time user connect manually by clicking the connect btn and interacting with MetaMask popup and then my dApp detect that connection is still established and use this connection. I can't find the solution, but i saw this in other dApps (Capture the ether for example) I use:
import detectEthereumProvider from '#metamask/detect-provider';
const provider = await detectEthereumProvider();
if (provider) {
connect(provider)
} else {
// kind of "Install the MetaMask please!"
}
function connect(provider) {
// How to check if the connection is here
if (//connection established) {
// Show the user connected account address
} else {
// Connect
provider.request({ method: "eth_requestAccounts" })
.then // some logic
}
}
I finally found a possible solution and it turned out to be as simple as it should be. There is an eth_accounts method in Ethereum JSON-RPC which allow us to ask for available accounts without actually requesting them. This way we can check if metamask is still connected (if there are any accounts) and avoid auto requesting or need of manually clicking "connect" every time. Simple example implementation could be:
// detect provider using #metamask/detect-provider
detectEthereumProvider().then((provider) => {
if (provider && provider.isMetaMask) {
provider.on('accountsChanged', handleAccountsChanged);
// connect btn is initially disabled
$('#connect-btn').addEventListener('click', connect);
checkConnection();
} else {
console.log('Please install MetaMask!');
}
});
function connect() {
ethereum
.request({ method: 'eth_requestAccounts' })
.then(handleAccountsChanged)
.catch((err) => {
if (err.code === 4001) {
console.log('Please connect to MetaMask.');
} else {
console.error(err);
}
});
}
function checkConnection() {
ethereum.request({ method: 'eth_accounts' }).then(handleAccountsChanged).catch(console.error);
}
function handleAccountsChanged(accounts) {
console.log(accounts);
if (accounts.length === 0) {
$('#connection-status').innerText = "You're not connected to MetaMask";
$('#connect-btn').disabled = false;
} else if (accounts[0] !== currentAccount) {
currentAccount = accounts[0];
$('#connection-status').innerText = `Address: ${currentAccount}`;
$('#connect-btn').disabled = true;
}
}
Use window.onload to initiate the isConnected() function when the webpage is loaded. The browser console will return a wallet address if it is connected.
window.onload = (event) => {
isConnected();
};
async function isConnected() {
const accounts = await ethereum.request({method: 'eth_accounts'});
if (accounts.length) {
console.log(`You're connected to: ${accounts[0]}`);
} else {
console.log("Metamask is not connected");
}
}
I assume you have already found Metamask docs on Ethereum Provider API. This section specifies three steps you need to do to make your app work:
Detect the Ethereum provider (window.ethereum)
Detect which Ethereum network the user is connected to
Get the user's Ethereum account(s)
Your snippet does the first part - it detects the provider.
As per this section, to detect network you can use the following code
const chainId = await ethereum.request({ method: 'eth_chainId' });
handleChainChanged(chainId);
ethereum.on('chainChanged', handleChainChanged);
function handleChainChanged(_chainId) {
window.location.reload();
}
And the most crucial part - fetching user account.
let currentAccount = null;
function handleAccountsChanged(accounts) {
if (accounts.length === 0) {
console.log('Please connect to MetaMask.');
} else if (accounts[0] !== currentAccount) {
currentAccount = accounts[0];
}
}
document.getElementById('connectButton', connect);
function connect() {
ethereum
.request({ method: 'eth_requestAccounts' })
.then(handleAccountsChanged)
.catch((err) => {
if (err.code === 4001) {
console.log('Please connect to MetaMask.');
} else {
console.error(err);
}
});
After the user logs in the first time, Metamask won't show the pop-up next time.
I think it's help you. In some case you noticedethereum.window.once('connect',()=>{}) is not worked and then disconnect event too.. i also face this problem and i don't know how to get userAccount address automatically after refresh so i started research on many youtube video and metamask api document. finally i got the answer.
import React, {useState,useEffect} from 'react';
import { ethers} from 'ethers';
function App(){
let [userAccount,setUserAccount] = useState({
isConnect:false,
Account:""
})
let isItConnect = async()=>{
let provider = new ethers.providers.Web3Provider(window.ethereum);
let accounts = await provider.send("eth_requestAccounts",[]);
console.log(accounts.length)
if(accounts.length>0){
return {
status:true,
userAddress:accounts[0]
}
}
else{
return {
status:false,
userAddress:""
}
}
}
let connect = async()=>{
let Status = await isItConnect();
localStorage.setItem('isConnected',Status.status)
setUserAccount((prev)=>{
return {...prev,Account:Status.userAddress}
})
}
window.ethereum.on('accountsChanged',async()=>{
localStorage.removeItem('isConnected');
setUserAccount((prev)=>{
return {...prev,Account:""}
})
connect()
})
useEffect(()=>{
let status = localStorage.getItem('isConnected')
if(status){
connect()
}
if(status === null){
if(window.ethereum.selectedAddress === null){
console.log('welcome User!')
}
else{
connect()
}
}
},[])
return (
<>
{userAccount.Account===""&& <button onClick={connect}>Connect Metamask!
</button>}
{userAccount.Account !==""&& <>
<p>{userAccount.Account}</p>
<p>Connected</p>
</>
)
}
Try using window.ethereum._state.account it will show array of accounts if connected else it will show an empty array, and use the length property to further access if connected to metamask or not.
This would get you the wallet address. returns false if not connected.
const getAccount = async () => await window.ethereum.request({method: 'eth_accounts'})[0] || false;
basic call from the DOM:
window.onload = event => {
const account = getAccount();
console.log(account ? `You're connected to: ${accounts}` : 'Metamask is not connected');
};
if using react:
componentDidMount() {
const account = getAccount();
console.log(account ? `You're connected to: ${accounts}` : 'Metamask is not connected');
}

window.solana not found in web3js

I want to connect a Solana wallet (phantom or any other) to a web application through the web3js library. I've read docs for most wallets and it seems like it's just as simple as await window.solana.request({ method: "connect" }); but window.solana is undefined in my case.
When I do console.log(window) I can see the Solana value with all its corresponding keys and values.
How can I do this?
I've found a working code that solved my issue. I am not sure what was the issue as I'm not very experienced with js, but the following code lets me connect to phantom.
I found this on StackOverflow on a similar thread, although I belive the original answer is missing some brackets.
Solana : Adding Sollet / Phantom Wallet Connect to my website - Steps?
const getProvider = async () => {
if ("solana" in window) {
await window.solana.connect(); // opens wallet to connect to
const provider = window.solana;
if (provider.isPhantom) {
console.log("Is Phantom installed? ", provider.isPhantom);
return provider;
}
} else {
document.write('Install https://www.phantom.app/');
}
};
window.onload = () => {
getProvider().then(provider => {
console.log('key', provider.publicKey.toString())
})
.catch(function(error){
console.log(error)
});
}
With your current implementation, everytime you refresh the app, you will get pop up to connect to the wallet. Instead you add {onlyIfTrusted:true} option to connect.
const getProvider = async () => {
if ("solana" in window) {
await window.solana.connect({onlyIfTrusted:true}); // opens wallet to connect to
const provider = window.solana;
if (provider.isPhantom) {
console.log("Is Phantom installed? ", provider.isPhantom);
return provider;
}
} else {
document.write('Install https://www.phantom.app/');
}
};
then instead of getting pop up when you reload the app, write a connection function to handle the connection when a user clicks on the button
const connectToWallet=async ()=>{
const {solana}=window
if(solana){
const response=await solana.connect()
console.log('address',response.publicKey.toString())
}
}
<button onClick={connectToWallet} >
Connect to Wallet
</button>
Now once user is connected, when you reload the app, it you wont get pop up to connect to the wallet
Is your website https enabled? If not then it won't work

how to use browser js parse sourcemap

in android logcat, I expect android webview raise js err with typescript source, following is my sample code
testdraft.ts
window.onerror = (message, file, line, column, err) => {
if (err !== undefined) {
(window as any).mapStackTrace(err.stack, (mappedStack:any) => {
console.log(mappedStack)
})
}
}
var m = () => {
throw new Error("pr")
}
m()
testdraft.js.map
{"version":3,"file":"testdraft.js","sourceRoot":"","sources":["testdraft.ts"],"names":[],"mappings":";AAAA,MAAM,CAAC,OAAO,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE;IAClD,IAAI,GAAG,KAAK,SAAS,EAAE;QAClB,MAAc,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,WAAe,EAAE,EAAE;YACzD,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QAC5B,CAAC,CAAC,CAAA;KACL;AACL,CAAC,CAAA;AAED,IAAI,CAAC,GAAG,GAAG,EAAE;IACT,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,CAAA;AACzB,CAAC,CAAA;AACD,CAAC,EAAE,CAAA"}
how to parse testdraft.js.map?
I know chrome://inspect can debug android webview, but I expect android logcat also raise backtrace with typescript source for app production stage analysis
I find "stacktrace-js" can convert bundle stacktrace to source stacktrace(or you can make own source-map parser)
see following:
testdraft.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<!--<script src="../node_modules/sourcemapped-stacktrace/dist/sourcemapped-stacktrace.js"></script>-->
<script src="../node_modules/stacktrace-js/dist/stacktrace.js"></script>
<script src="./testdraft.js"></script>
</body>
</html>
testdraft.ts
window.onerror = (message, file, line, column, err) => {
if (err !== undefined) {
(window as any).StackTrace.fromError(err).then((err: any) => {
console.log(err.join("\n"))
}).catch(() => {
})
}
}
var m = () => {
throw new Error("pr")
}
m()
use tsc compile testdraft.ts to testdraft.js + testdraft.js.map and run testdraft.html
so the android webview solution is, use webview in pageStated run js load stacktrace-js and following code:
window.onerror = (message, file, line, column, err) => {
if (err !== undefined) {
(window as any).StackTrace.fromError(err).then((err: any) => {
console.log(err.join("\n"))
}).catch(() => {
})
}
}
or maybe better way, don't use webview api run above code, use webpack prepend above code in bundle.js first

Uncaught TypeError: MicrosoftGraph.MSALAuthenticationProviderOptions is not a constructor

I am revisiting an old side project which I last updated about 1.5 years ago (at this time it was working without errors).
From memory, I believe I used this tutorial at the time to create the basic foundation for the app.
On loading the app now I am getting this error in Chrome dev tools:
Uncaught TypeError: MicrosoftGraph.MSALAuthenticationProviderOptions is not a constructor
at graph.js:1
The code at line 1 of graph.js is:
const authProviderOptions = new MicrosoftGraph.MSALAuthenticationProviderOptions(scopes);
Googling for the error above doesn't seem to lead to relevant results.
Below are the various parts of the app I think are relevant in troubleshooting the error.
For reference, it seems the the version of MSAL I am using needs to be 'upgraded' from this (https://alcdn.msauth.net/lib/1.3.0/js/msal.js) to this (https://alcdn.msauth.net/browser/2.18.0/js/msal-browser.min.js), however I haven't tried that yet in case it causes additional issues I do not yet know how to resolve.
index.html
<!-- msal -->
<!-- from: https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-core -->
<script type="text/javascript" src="https://alcdn.msauth.net/lib/1.3.0/js/msal.js" integrity="sha384-xeOjp8/l8VazdeNFRbrC9LWPR1InyrS8E1Na/0lv6V2r09iwX6vJC47FXlczokMi" crossorigin="anonymous"></script>
<!-- javascript sdk -->
<!-- from: https://github.com/microsoftgraph/msgraph-sdk-javascript -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/#microsoft/microsoft-graph-client/lib/graph-js-sdk.js"></script>
<!-- the logic of the app's functionality proceeds in the order below -->
<!-- set up click handlers etc -->
<script type="text/javascript" src="js/ui.js"></script>
<!-- define the configuration for msal -->
<script type="text/javascript" src="js/config.js"></script>
<!-- define authentication logic -->
<script type="text/javascript" src="js/auth.js"></script>
<!-- set up graph client and make api requests -->
<script type="text/javascript" src="js/graph.js"></script>
</body>
config.js
// check if running locally and set redirect uri accordingly
var hostname = location.hostname;
if (hostname === "localhost" || hostname === "127.0.0.1") {
console.log("running on localhost");
var redirect_uri = "http://localhost:8080";
}
else {
console.log("not running on localhost");
var redirect_uri = "https://somedomain.com";
}
// msal options
const msalConfig = {
auth: {
// this is the client/application id visible at:
// https://aad.portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps
clientId: "*******",
// this is the directory/tenant id visible at:
// https://aad.portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/client-id-is-here/isMSAApp/
redirectUri: redirect_uri,
authority: "https://login.microsoftonline.com/*******"
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: false,
forceRefresh: false
}
// ,
// system: {
// logger: new Msal.Logger(
// loggerCallback ,{
// level: Msal.LogLevel.Verbose,
// piiLoggingEnabled: false,
// correlationId: '1234'
// }
// )
// }
};
// define application permissions
const scopes = ['directory.accessasuser.all', 'directory.readwrite.all', 'group.readwrite.all', 'groupmember.readwrite.all', 'openid', 'profile', 'sites.read.all', 'user.read', 'tasks.readwrite' ];
function loggerCallback(logLevel, message, containsPii) {
console.log(message);
}
auth.js
const msalApplication = new Msal.UserAgentApplication(msalConfig);
if (msalApplication.getAccount()) {
console.log("you refreshed the page and you are still signed in");
toggle_sign_in_and_sign_out();
}
const loginRequest = {
scopes: scopes
}
async function sign_in() {
try {
await msalApplication.loginPopup(loginRequest);
console.log('id_token acquired at: ' + new Date().toString());
var account_info = msalApplication.getAccount();
if (account_info) {
console.log("sign in success");
console.log(account_info);
show_response("n/a",account_info);
toggle_sign_in_and_sign_out();
}
} catch (error) {
console.log("sign in error");
console.log(error);
}
}
function sign_out() {
msalApplication.logout();
toggle_sign_in_and_sign_out();
}
graph.js
const authProviderOptions = new MicrosoftGraph.MSALAuthenticationProviderOptions(scopes);
const authProvider = new MicrosoftGraph.ImplicitMSALAuthenticationProvider(msalApplication, authProviderOptions);
const client_options = {
authProvider
};
const client = MicrosoftGraph.Client.initWithMiddleware(client_options);
async function get_stuff(endpoint) {
try {
if (endpoint === "my_details") {
var path = "/me";
var response = await client.api(path)
.get();
}
// there are different handlers here for different api requests
console.log(response);
} catch (error) {
throw error;
}
}
Below are the changes I needed to make:
(I have one remaining issue where, when I reload the page, the graph requests do not work, the error is: TypeError: Cannot read properties of undefined (reading 'api') - perhaps i need to call initializeGraphClient() again on page load so that graphClient is not undefined?...)
index.html
Change reference to MSAL and SDK scripts:
<script src="https://alcdn.msauth.net/browser/2.16.1/js/msal-browser.min.js" integrity="sha384-bPBovDNeUf0pJstTMwF5tqVhjDS5DZPtI1qFzQI9ooDIAnK8ZCYox9HowDsKvz4i" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/#microsoft/microsoft-graph-client#3.0.0/lib/graph-js-sdk.js"></script>
<script src="https://cdn.jsdelivr.net/npm/#microsoft/microsoft-graph-client#3.0.0/lib/graph-client-msalBrowserAuthProvider.js"></script>
config.js
Add the mailboxsettings.read scope, so that the new getUser() function defined in graph.js works.
const scopes = ['directory.accessasuser.all', 'directory.readwrite.all', 'group.readwrite.all', 'groupmember.readwrite.all', 'mailboxsettings.read', 'openid', 'profile', 'sites.read.all', 'tasks.readwrite', 'user.read' ];
auth.js
Multiple changes - this is the new contents:
const msalInstance = new msal.PublicClientApplication(msalConfig);
if (sessionStorage.getItem("graphUser") !== null) {
console.log("you refreshed the page and you are still signed in");
toggle_sign_in_and_sign_out();
}
const loginRequest = {
scopes: scopes
}
async function sign_in() {
try {
// use MSAL to login
const authResult = await msalInstance.loginPopup(loginRequest);
console.log('id_token acquired at: ' + new Date().toString());
// initialize the graph client
initializeGraphClient(msalInstance, authResult.account, loginRequest.scopes);
// get the user's profile from Graph
var user = await getUser();
// save the profile in session
sessionStorage.setItem('graphUser', JSON.stringify(user));
toggle_sign_in_and_sign_out();
} catch (error) {
console.log(error);
}
}
function sign_out() {
sessionStorage.removeItem('graphUser');
msalInstance.logout();
toggle_sign_in_and_sign_out();
}
graph.js
Multiple changes - here is new contents:
let graphClient = undefined;
function initializeGraphClient(msalInstance, account, scopes)
{
// create an authentication provider
const authProvider = new MSGraphAuthCodeMSALBrowserAuthProvider
.AuthCodeMSALBrowserAuthenticationProvider(msalInstance, {
account: account,
scopes: scopes,
interactionType: msal.InteractionType.PopUp
});
// initialize the Graph client
graphClient = MicrosoftGraph.Client.initWithMiddleware({authProvider});
}
console.log("graphClient:");
console.log(graphClient);
async function getUser() {
return await graphClient
.api('/me')
// only get the fields used by the app
.select('id,displayName,mail,userPrincipalName,mailboxSettings')
.get();
}
async function get_stuff(endpoint) {
console.log("endpoint is: " + endpoint);
try {
if (endpoint === "my_details") {
var path = "/me";
var response = await graphClient.api(path)
.get();
}
// there are different handlers here for different api requests
console.log(response);
} catch (error) {
throw error;
}
}
References
Migrating from MSAL 1.x to MSAL 2.x
Be aware however, that there is a lot of documentation out there on the topic of authentication (and the different ways it can be done) and I found it very easy to become lost.
The JavaScript SDK 'automates' some of the manual steps but I had to read the tutorial code in parallel with the MSAL documentation very carefully to see how it all fit together.

sendMessage() of react-native-firebase/messaging doesn't work in version 6.2.0

I have used react-native-firebase version 6.2.0
When I use react-native-firebase/messaging, I found out any sendMessage() function doesn’t work.
(I use android devices and virtual machine.)
I just follow the document here
At first, I registered remoteNotification and got FCM token from it. => init()
Then, I sent upstream remoteMessage => sendMessage()
But, I could not find out where are the messages. => could not receive any messages in device and in firebase cloud messaging console.
When I sent messages from firebase cloud messaging console, I could got the message at devices.
import messaging from '#react-native-firebase/messaging';
import firestore from '#react-native-firebase/firestore';
import store from 'project3/redux/store.js';
export async function init() {
const enabled = await messaging().hasPermission();
if (enabled) {
console.log('user has permissions');
} else {
console.log('user doesnt have permission');
const enabled2 = await messaging().requestPermission();
if (enabled2) {
console.log('requestPermission');
} else {
console.log('not requestPermission');
}
}
console.log('getToken');
await messaging().registerForRemoteNotifications();
const fcmToken = await messaging().getToken();
const uid = store.getState().email;
console.log('fmcToken : ' + fcmToken);
await firestore()
.doc(`users/${uid}`)
.update({
fcmToken: fcmToken,
});
console.log(
'isRegisteredForRemoteNotifications ' +
messaging().isRegisteredForRemoteNotifications,
);
messaging().onMessage(async remoteMessage => {
console.log('FCM Message Data:', remoteMessage.data);
});
messaging().onSendError(event => {
console.log(event.messageId);
console.log(event.error);
});
}
export async function sendMessage() {
console.log('sendMessage');
await messaging()
.sendMessage({
data: {
loggedIn: Date.now().toString(),
uid: store.getState().email,
},
})
.then(msg => {
console.log(msg);
});
}
Please help me.
I found many cases about below version 5.x.x of react-native-firebase.
But, there are very few cases about 6.x.x and guide isn't also sufficient.
You may save my weeks.

Categories

Resources