How to establish a web3 provider connection from a Vue.js DApp? - vue.js

I am intended to interact with Rootstock blockchain
from a vue.js DApp to track wallet balance and send RBTC.
​
I want to establish a Metamask connection
and use ethers.js web3 provider
to interact with the Rootstock network.
​
I created a Pinia storage
to keep all the web3 data available for the whole app.
Here is a concise version of what I've done so far:
​
import { ref } from 'vue';
import { defineStore } from 'pinia';
import { providers } from 'ethers';
​
export const useRootstockStore = defineStore('rootstock', () => {
const balance = ref(0);
const address = ref('');
const provider = ref(null);
​
const getBalance = async () => {
balance.value = await provider.value.getBalance(address.value);
};
​
const connect = async () => {
await window.ethereum.request({
method: 'eth_requestAccounts',
});
provider.value = new providers.Web3Provider(window.ethereum);
[address.value] = await provider.value.listAccounts();
};
​
...
});
​
Within the storage I have:
​
provider ref which is supposed to store a reference to web3 provider
address ref keeping the Metamask wallet address
balance ref storing the wallet balance
connect function which establishes Metamask connection and instantiates ethers web3 provider
getBalance function which queries the provider for RBTC balance of the wallet
​
After calling the connect function,
the app connects to Metamask and
seems to establish a connection with Rootstock,
however when I try to query the wallet's RBTC balance,
I keep getting the following error:
​
TypeError: 'get' on proxy: property '_network' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '#<Object>' but got '#<Object>')
at Proxy.<anonymous> (base-provider.ts:820:22)
at Generator.next (<anonymous>)
at fulfilled (formatter.ts:523:1)
​
What am I missing here?

Swap out ref,
and replace it with
computed.
Instead of using a ref to store the provider:
const provider = ref(null);
you should use computed to store the provider instead:
const provider = computed(() => new providers.Web3Provider(window.ethereum));
So, the whole script should be as follows:
import { ref, computed } from 'vue';
...
export const useRootstockStore = defineStore('rootstock', () => {
const balance = ref(0);
const address = ref('');
const provider = computed(() => new providers.Web3Provider(window.ethereum));
const getBalance = async () => {
balance.value = await provider.value.getBalance(address.value);
};
const connect = async () => {
await window.ethereum.request({
method: 'eth_requestAccounts',
});
[address.value] = await provider.value.listAccounts();
};
...
});
This should resolve your issue.

Related

Error: unknown account #0 (operation="getAddress", code=UNSUPPORTED_OPERATION, version=providers/5.7.1)

I want to test my function withdraw of my Vuejs app.
If my wallet Metamask is connected, then I can click on a button withdraw to get the money of my contract. The function works. Now I want to test it with Jest.
withdraw: async function(){
if(typeof window.ethereum !== 'undefined') {
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const contract = new ethers.Contract(this.contractAddress, NftContract.abi, signer);
try {
const transaction = await contract.withdraw();
await transaction.wait();
this.setSuccess('The withdrawal is successful');
}
catch(err) {
console.log(err);
this.setError('An error occured to withdraw');
}
}
}
I'm using eth-testing (https://www.npmjs.com/package/eth-testing?activeTab=readme) to mock the interaction with my smart contract. My test with Jest:
let wrapper;
const testingUtils = generateTestingUtils({ providerType: "MetaMask" });
beforeAll(() => {
// use this to check the state of anything in the view
wrapper = shallowMount(NFTMintComponent);
// Manually inject the mocked provider in the window as MetaMask does
global.window.ethereum = testingUtils.getProvider();
})
afterEach(() => {
// Clear all mocks between tests
testingUtils.clearAllMocks();
//jest.restoreAllMocks();
})
it('when the owner withdraw the amount of the contract, the balance of the contract should be 0 and a successful message should appear (withdraw function)', async () => {
// Start with not connected wallet
testingUtils.mockNotConnectedWallet();
// Mock the connection request of MetaMask
const account = testingUtils.mockRequestAccounts(["0xe14d2f7105f759a100eab6559282083e0d5760ff"]);
//allows to mock the chain ID / network to which the provider is connected --> 0x3 Ropsten network
testingUtils.mockChainId("0x3");
// Mock the network to Ethereum main net
testingUtils.mockBlockNumber("0x3");
const abi = NftContract.abi;
// An address may be optionally given as second argument, advised in case of multiple similar contracts
const contractTestingUtils = testingUtils.generateContractUtils(abi);
const transaction = await contractTestingUtils.mockTransaction("withdraw", '0x10Bc587867D87d1Ea1Cd62eac01b6DD027c182E9');
await wrapper.vm.withdraw();
});
I got the error: Error: unknown account #0 (operation="getAddress", code=UNSUPPORTED_OPERATION, version=providers/5.7.1) for my transaction contract.withdraw();
It's like the contract doesn't recognize my account even if I mocked my account at the beginning of the test with const account = testingUtils.mockRequestAccounts(["0xe14d2f7105f759a100eab6559282083e0d5760ff"]);
How can I fix that problem ?

Get ip address and pass to Apollo client before react native renders (expo)

in my app.js I have the following:
import React from 'react';
import { ApolloClient, InMemoryCache, createHttpLink, ApolloProvider } from '#apollo/client';
import * as Network from 'expo-network';
Per Apollo documentation I am assigning a uri to httpLink. Currently the uri is static but I would like this uri to be dynamic and update before the app renders.
const httpLink = createHttpLink({
uri: `http://192.168.1.165:5000/graphql`,
});
// get headers
const authLink = setContext( async (_, { headers }) => {
// return headers
...
});
// create apollo client
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
Finally rendering the app and passing client to Apollo:
export default () => {
return (
<ApolloProvider client={client}>
<SomeModule>
</SomeModule>
</ApolloProvider>
);
};
I'm attempting to use Expo to get the client IP address. The expo docs give a limited example of getting the ip address which I've interpreted to mean something like:
async function getLocalIPAddress() {
const ipAddress = await Network.getIpAddressAsync();
// do something
}
How should I invoke the function to get the IP address before export default is invoked?

access redux persist outside components

I'm trying to access a redux store with redux persist in a service for my react native app.
I need a specific token to set a websocket connection.
My code so far:
./redux/Store.js:
const persistedReducer = combineReducers({
tokens: persistReducer(secureConfig, TokensReducer),
});
const store = createStore(persistedReducer);
const configureStore = () => {
const persistor = persistStore(store);
return { persistor, store };
};
export default configureStore;
./redux/reducers/TokenReducer
const initialState = {
accessToken: null,
refreshToken: null
}
const TokensReducer = (state = initialState, action) {
// reducer
};
export default TokensReducer;
./service/websocket.js
import configureStore from '../redux/Store';
const { store } = configureStore();
console.log(store.getState().tokens);
The problem is, I'm not getting the persisted content, but I'm getting the initial state (accessToken = null, refreshToken = null).
When I access the store from inside my app (inside components inside and ), I get the correct values.
Edit:
when I wrap the console.log in a setTimeout() of let's say 1 second, it works! So it asynchronous, but how can I create my code to wait for it and not using setTimeout?

how to handle failed silent auth error in auth0

I followed spa react quick start guide and it worked fine for more than a month. Recently i had this error and it is logged on auth0 as 'failed silent error' with no further information. I have been told that it is because of the browsers cookie updates and recommended to use new beta release of auth0-spa-js and change cache location to local storage. And it didn't work either.
The code is as follows:
auth_config.json:
{
"domain": "dev.........eu.auth0.com",
"clientId": "....eEKkQ.............",
"redirect_uri": "https://localhost:8080",
"audience": "https://.......herokuapp.com/v1/....",
"cacheLocation": "localstorage"
}
and
react-auth0-wrapper.js:
import React, { useState, useEffect, useContext } from "react";
import createAuth0Client from "#auth0/auth0-spa-js";
const DEFAULT_REDIRECT_CALLBACK = () =>
window.history.replaceState({}, document.title, window.location.pathname);
export const Auth0Context = React.createContext();
export const useAuth0 = () => useContext(Auth0Context);
export const Auth0Provider = ({
children,
onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
...initOptions
}) => {
const [isAuthenticated, setIsAuthenticated] = useState();
const [user, setUser] = useState();
const [auth0Client, setAuth0] = useState();
const [loading, setLoading] = useState(true);
const [popupOpen, setPopupOpen] = useState(false);
useEffect(() => {
const initAuth0 = async () => {
const auth0FromHook = await createAuth0Client(initOptions);
setAuth0(auth0FromHook);
if (window.location.search.includes("code=")) {
const { appState } = await auth0FromHook.handleRedirectCallback();
onRedirectCallback(appState);
}
const isAuthenticated = await auth0FromHook.isAuthenticated();
setIsAuthenticated(isAuthenticated);
if (isAuthenticated) {
const user = await auth0FromHook.getUser();
setUser(user);
}
setLoading(false);
};
initAuth0();
// eslint-disable-next-line
}, []);
const loginWithPopup = async (params = {}) => {
setPopupOpen(true);
try {
await auth0Client.loginWithPopup(params);
} catch (error) {
console.error(error);
} finally {
setPopupOpen(false);
}
const user = await auth0Client.getUser();
setUser(user);
setIsAuthenticated(true);
};
const handleRedirectCallback = async () => {
setLoading(true);
await auth0Client.handleRedirectCallback();
const user = await auth0Client.getUser();
setLoading(false);
setIsAuthenticated(true);
setUser(user);
};
return (
<Auth0Context.Provider
value={{
isAuthenticated,
user,
loading,
popupOpen,
loginWithPopup,
handleRedirectCallback,
getIdTokenClaims: (...p) => auth0Client.getIdTokenClaims(...p),
loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p),
getTokenSilently: (...p) => auth0Client.getTokenSilently(...p),
getTokenWithPopup: (...p) => auth0Client.getTokenWithPopup(...p),
logout: (...p) => auth0Client.logout(...p)
}}
>
{children}
</Auth0Context.Provider>
);
};
What is wrong with this code, any help appreciated. Or i can use a different method, i just followed the docs, it doesn't matter as long as it authenticates.
Thanks
I know this has been hanging around for a bit, but i was running into a similar issue.
As I understand it the createAuth0Client helper factory runs the getTokenSilently function by default as part of the set up to re-authenticate users every browser refresh. The problem i was having was that the call to getTokenSilently was erroring, meaning that auth0FromHook was never set and the auth0client never set in state. Because auth0client was undefined, it was then impossible to call loginwithredirect, which is the behaviour i wanted to achieve.
Basically i wanted it to auth silently, but if it failed, send to the log in screen, but that's impossible because the auth0client was undefined, resulting in a cannot call loginwithredirect of undefined error. It seems that (sadly) in the current stable version of the #auth0/auth0-spa-js library (1.6.5 at time of writing) there is no way to bypass getTokenSilently when initialising the client. However in the current beta (1.7.0-beta.5) (Here is a list of versions) they have exposed the Auth0Client class itself, so if you want to move to that version the code could be tweaked with something like....
initAuth0().catch( e => {
const newClient = new Auth0Client(initOptions);
setAuth(newClient);
})
and then in any protected components you can check the loading is finished and if isAuthenticated is still falsey, you should be able to redirect to login despite an error occurring during the getSilentToken.
== NON BETA OPTION
The alternative in the current api would be to perhaps set max_age to 0 or 1 in the initOptions, to force a re-login, and maybe setting prompt to "login" on the second attempt to initialize the authClient

Connexion object singleton in react native

I'm trying to create a singleton service class in which I instanciate a connection object, which connect to the backend, in order to reuse the connection object in every component, so I've done that:
const {
Kuzzle,
WebSocket
} = require('kuzzle-sdk');
class KuzzleService {
static instance = null;
static async createInstance() {
var object = new KuzzleService();
object.kuzzle = new Kuzzle(
new WebSocket('localhost'),{defaultIndex: 'index'}
);
await object.kuzzle.connect();
const credentials = { username: 'user', password: 'pass' };
const jwt = await object.kuzzle.auth.login('local', credentials);
return object;
}
static async getInstance () {
if (!KuzzleService.instance) {
KuzzleService.instance = await KuzzleService.createInstance();
}
return KuzzleService.instance;
}
}
const kuzzleService = KuzzleService.getInstance();
export default kuzzleService;
But when I import the service in a component as follow:
import kuzzleService from "../services/kuzzle-service.js";
And I print that:
async componentDidMount(){
console.log(JSON.stringify(kuzzleService.kuzzle));
}
It gives me "undefined". Should I import the service another way ?
This is probably because when you export kuzzleService, the promise given by .getInstance() isn't resolved yet.
You should export the .getInstance function and await it in componentDidMount, like that:
export default KuzzleService; // export the singleton directly
async componentDidMount(){
const kuzzle = await KuzzleService.getInstance();
console.log(kuzzle);
}