Gnosis Safe MultiSig wallet automatically denying the transfer of Ether to another account - web3js

I've been integrating my dApp that has Gnosis Safe Wallet Support, but I've having an issue when I try and request Ether to be transferred from the Gnosis Wallet to another contract.
Here's the code that makes the request:
const signer = web3reactContext.library.getSigner(web3reactContext.account).connectUnchecked();
const tx = await signer.sendTransaction({
to: "...",
value: ethers.utils.parseEther(".005")
});
I'm getting this error in the console when the user hits the button that calls this function:
Object { code: -32000, message: "Request rejected" }
Anyone experienced this error before>

Related

Thirdweb error: This action requires a connected wallet to sign the transaction. Please pass a valid signer to the SDK

So I have this code:
import { ThirdwebSDK } from "#thirdweb-dev/sdk";
import { ConnectWallet, useAddress } from "#thirdweb-dev/react";
export default function DonationPage() {
let address = useAddress()
async function sendCoins() {
try {
if (selectedCoin == 'eth') {
} else {
const sdk = new ThirdwebSDK("mainnet");
const contract = await sdk.getContract('ADDRESS_TO_USDC_CONTRACT');
await contract.erc20.transferFrom(address || '0x',CORPORATE_WALLET || '0x', donationAmount);
}
} catch (error: any) {
alert(error.message)
}
}
return (<>...</>)
}
So i'm using ConnectWallet button to get address and using TrustWallet for this, and trying to transfer tokens from 'address' to a corporate wallet, with a specified donation amount. However, I receive this error "This action requires a connected wallet to sign the transaction. Please pass a valid signer to the SDK.".
There seems to be no documentation on this online, and ChatGPT won't help either. Does someone know how to fix this?
Also, how to send Eth to CORPORATE_WALLET using Thirdweb?
There seems to be no documentation on this online
Well there is complete documentation on the SDK on ThirdWeb website.
Since you're performing transaction on the contract you cannot initialize the SDK without a private key or signer. Using neither returns read-only contracts
Because you're performing the operations in the frontend, fromSigner method is recommended. Access the user's wallet and call for the signer, use this to initialize the SDK instance with the .fromSigner() method.
fromSigner documentation
ThirdWeb official fromSigner example with React
Also linking documentation for private key method, just in case you wish to approach that way.

How to call Solidity Function to return Ether from Smart Contract?

I have deployed a smart contract on a local truffle project and I am trying to interact with it in a React project using web3. The following solidity function should send Ether what was previously deposited in the contract to a user address on a boolean condition:
function Payout() public{
require( voteEndTime< block.timestamp, "Voting Time is not up. Please come back later" );
Voter storage sender = voters[msg.sender];
if (negativeVotes > positiveVotes){
require(!sender.option, "Wrong Vote. Stake is distributed among winners");
payable(address(msg.sender)).transfer((stakes*sender.amount) / negativeStakes);
}
else if (positiveVotes > negativeVotes){
require(sender.option, "Wrong Vote. Stake is distributed among winners");
payable(address(msg.sender)).transfer((stakes*sender.amount) / positiveStakes);
}
else{
payable(address(msg.sender)).transfer((stakes*sender.amount) / stakes);
}
}
The contract is definitely able to read the user's address using msg.sender because it has worked in the other functions I have. Every other function in the contract is also working fine. I can interact with it and I am able to send Ether to it. The problem occurs when I am trying to return the Ether stored in the contract to an account. I am trying to call my Payout() function using the following web3 call in React on button click:
var response = await BallotContract.methods.Payout().send({ from: account, gas: 310000 })
I have specified a higher gas limit, because the contract runs out of gas if I try to use the gas estimation seen below. The function this call is present in looks like this:
const giveMeMoney = async (e) => {
const web3 = await new Web3(window.ethereum);
await window.ethereum.enable();
var Accounts = await web3.eth.getAccounts()
account = Accounts[0]
console.log(account)
const gas = await BallotContract.methods.Payout().estimateGas();
console.log(gas)
var response = await BallotContract.methods.Payout().send({ from: account, gas: 310000 })
}
I am able to access the function from the frontend and it is returning the correct string if a "require" condition is not met. My problem is that the contract does not return any Ether if the conditions are met and this line:
payable(address(msg.sender)).transfer((stakes*sender.amount) / positiveStakes);
...is accessed. I am getting the following error:
Uncaught (in promise) Error: Returned error: VM Exception while processing transaction: revert
at Object.ErrorResponse (errors.js:30)
at onJsonrpcResult (index.js:162)
at XMLHttpRequest.request.onreadystatechange (index.js:123)
ErrorResponse # errors.js:30
Now I am unsure what could be the problem, because the contract is running perfectly fine if I test it in Remix. Does anybody see the problem or have a workaround for this kind of problem?
The "out of gas" error is caused by the transfer function. This function has a gas limit of 2100; I recommend you use call, you can check how here.
Also the boolean value of the Voter struct defaults to false. So if the negative votes win, but the user didn't vote, they still can try to claim the reward, even if the amount is 0. I recommend you check how to use an enum, it can be very useful.

How to tell if a user is logged in with http only cookies and JWT in react (client-side)

So I'm trying to follow the security best practices and I'm sending my JWT token over my React app in a only-secure http-only cookie.
This works fine for requests but the major issue I find with this approach is, how can I tell if the user is logged-in on client-side if I can't check if the token exists? The only way I can think of is to make a simple http to a protected endpoint that just returns 200.
Any ideas? (not looking for code implementations)
The approach I would follow is to just assume the user is logged in, and make the desired request, which will send the httpOnly token automatically in the request headers.
The server side should then respond with 401 if the token is not present in the request, and you can then react in the client side accordingly.
Using an endpoint like /api/users/me
Server-side
Probably you don't only need to know if a user is already logged in but also who that user is. Therefore many APIs implement an endpoint like /api/users/me which authenticates the request via the sent cookie or authorization header (or however you've implemented your server to authenticate requests).
Then, if the request is successfully authenticated, it returns the current user. If the authentication fails, return a 401 Not Authorized (see Wikipedia for status codes).
The implementation could look like this:
// UsersController.ts
// [...]
initializeRoutes() {
this.router.get('users/me', verifyAuthorization(UserRole.User), this.getMe);
}
async getMe(req: Request, res: Response) {
// an AuthorizedRequest has the already verified JWT token added to it
const { id } = (req as AuthorizedRequest).token;
const user = await UserService.getUserById(id);
if (!user) {
throw new HttpError(404, 'user not found');
}
logger.info(`found user <${user.email}>`);
res.json(user);
}
// [...]
// AuthorizationMiddleware.ts
export function verifyAuthorization(expectedRole: UserRole) {
// the authorization middleware throws a 401 in case the JWT is invalid
return async function (req: Request, res: Response, next: NextFunction) {
const authorization = req.headers.authorization;
if (!authorization?.startsWith('Bearer ')) {
logger.error(`no authorization header found`);
throw new HttpError(401, 'unauthorized');
}
const token = authorization.split(' ')[1];
const decoded = AuthenticationService.verifyLoginToken(token);
if (!decoded) {
logger.warn(`token not verified`);
throw new HttpError(401, 'unauthorized');
}
(req as AuthorizedRequest).token = decoded;
const currentRole = UserRole[decoded.role] ?? 0;
if (currentRole < expectedRole) {
logger.warn(`user not authorized: ${UserRole[currentRole]} < ${UserRole[expectedRole]}`);
throw new HttpError(403, 'unauthorized');
}
logger.debug(`user authorized: ${UserRole[currentRole]} >= ${UserRole[expectedRole]}`);
next();
};
}
Client-side
If the response code is 200 OK and contains the user data, store this data in-memory (or as alternative in the local storage, if it doesn't include sensitive information).
If the request fails, redirect to the login page (or however you want your application to behave in that case).

React and Nodemailer

I am running VSCode, Nodejs, Nodemailer, and Reactjs in a Windows machine, but I cannot get Nodemailer to send email. According to the instructions in the web, it should work. Finally I did the following: I created two empty folders in both of which I ran node init, installed Nodemailer, and copied the email sending code. In the other folder I also ran create-react-app. Then I edited the files just enough to get the sending code running.
In the first folder it works without problems, but in the folder with React, it does not do anything. Not even the usual following if(error)/else(success) statements get executed, they are just jumped over. However, the code before and after the transporter.sendMail (or .verify) part are executed... Anyone know why this happens or how to fix it?
This is the code I run in both cra and the non-cra folders:
const nodemailer = require("nodemailer");
const SendEmail = message => {
const transporter = nodemailer.createTransport({
service: "Gmail",
auth: {
user: "from#gmail.com",
pass: "xxxxxxxx"
}
});
transporter.verify(function(error) {
if (error) {
console.log(error);
} else {
console.log("Server is ready to take our messages");
}
});
const mailOptions = {
from: "from#gmail.com",
to: "to#gmail.com",
subject: "Subject",
text: message,
html: "<b>Html</b>"
};
transporter.sendMail(mailOptions, (err, info) => {
if (err) console.log(err);
else console.log(info.response);
});
};
module.exports = SendEmail;
Tim
Gmail has spam filter to prevent spam, so most probably, you may get it pass through sometime and not most time without proper configuration.
and it is not a good idea to send your email in your client app, such as react. Since everyone can access to your email and password to do nasty thing, which is not really a good idea.
Best practice is to request your node server to send mail.
Other than, I noticed that you used gmail to do that. There are some free mail fake stmp server that you can do spamming without the mail provider to flag you as spam user. Such as mailTrap, if you are just interested to test, is react able to send email, try it with mailtrap. I never do it, but still it is better than using your own email provider, as they might have filter rules about it, could be the reason, you are not able to send it.

AppEngine channel API: duplicate messages client side

I am trying to use the Channel API to push updates from server to the client. The flow is the user presses a button which triggers a server side action that generates a lot of logs. I want to display the logs to the user "in real time".
When I first load the page it I get all the messages, no problem. If I trigger the action a second time without refreshing the page in my browser, then all messages appear twice. Here is the set up portion of the channel that is tied to the page onLoad event. With resulting console logs I gathered that the onMessage() method is being invoked more than once when the page is not refreshed. Looks like I need to "kill" earlier sockets in some way, but could not find a way in the official documentation. Can someone point me in the right direction to get rid of the spurious messages?
// First fetch a token for the async communication channel and
// create the socket
$.post("/app/channels", {'op':'fetch', 'id' : nonce},
function (data, status, xhr) {
if (status == "success") {
data = JSON.parse(data);
token = data["token"];
console.log("Cookie: " + get_mp_did() + "; token: " + token);
var channel = new goog.appengine.Channel(token);
var handler = {
'onopen': onOpened,
'onmessage': onMessage,
'onerror': function() {
$("#cmd_output").append('Channel error.<br/>');
},
'onclose': function() {
$("#cmd_output").append('The end.<br/>');
$.post("/app/channels", {'op':'clear'});
}
};
var socket = channel.open(handler);
socket.onopen = onOpened;
socket.onmessage = onMessage;
}
});
onOpened = function() {
$("#cmd_output").empty();
};
onMessage = function(data) {
message = JSON.parse(data.data)['message'];
$("#cmd_output").append(message);
console.log('Got this sucker: ' + message);
}
If I understand your post and code correctly, the user clicks on a button which calls the $.post() function. The server code is responsible to create the channel in GAE as response to a /app/channels request. I think that your server in fact creates a new channel client ID / token with every subsequent request. Since the page is not reloaded, any subsequent request would add a new channel to this client. And all these channels would be still connected (hence, no page refresh).
I assume your server code has all channels associated to a user, and you send the message to a user utilizing all channels? Such pattern would result in this behavior. You can verify my assumption by clicking 3 or four times on the button with-out page refresh. The log output would be multiplied by the factor of 3 or 4.
I suggest, that you store the token in the client and on the server. Then make a modification to your client JS. If a channel is already created store the token value and provide it to any subsequent request to /app/channels. Modify the server so it will not create a new channel, if a token is provided with the request. If the token links to an existing valid channel, re-use the channel and return the same token in the response. You may need to add some more details for disconnected or expired channels, maybe also a cron-job to delete all expired channels after a while.

Resources